Index: icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
===================================================================
--- icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java (revision 39589)
+++ icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java (working copy)
@@ -1,6272 +1,2085 @@
-// © 2016 and later: Unicode, Inc. and others.
+// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
-/*
- *******************************************************************************
- * Copyright (C) 1996-2016, International Business Machines Corporation and
- * others. All Rights Reserved.
- *******************************************************************************
- */
package com.ibm.icu.text;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
import java.math.BigInteger;
+import java.math.RoundingMode;
import java.text.AttributedCharacterIterator;
-import java.text.AttributedString;
-import java.text.ChoiceFormat;
import java.text.FieldPosition;
-import java.text.Format;
+import java.text.ParseException;
import java.text.ParsePosition;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import com.ibm.icu.impl.ICUConfig;
-import com.ibm.icu.impl.PatternProps;
-import com.ibm.icu.impl.Utility;
+import com.ibm.icu.impl.number.AffixPatternUtils;
+import com.ibm.icu.impl.number.Endpoint;
+import com.ibm.icu.impl.number.Format.SingularFormat;
+import com.ibm.icu.impl.number.FormatQuantity4;
+import com.ibm.icu.impl.number.Parse;
+import com.ibm.icu.impl.number.PatternString;
+import com.ibm.icu.impl.number.Properties;
+import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
+import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
+import com.ibm.icu.impl.number.formatters.ScientificFormat;
+import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
+import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder.SignificantDigitsMode;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.math.MathContext;
-import com.ibm.icu.text.PluralRules.FixedDecimal;
+import com.ibm.icu.text.PluralRules.IFixedDecimal;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.Currency.CurrencyUsage;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
-import com.ibm.icu.util.ULocale.Category;
-/**
- * {@icuenhanced java.text.DecimalFormat}.{@icu _usage_}
- *
- * DecimalFormat
is a concrete subclass of {@link NumberFormat} that formats
- * decimal numbers. It has a variety of features designed to make it possible to parse and
- * format numbers in any locale, including support for Western, Arabic, or Indic digits.
- * It also supports different flavors of numbers, including integers ("123"), fixed-point
- * numbers ("123.4"), scientific notation ("1.23E4"), percentages ("12%"), and currency
- * amounts ("$123.00", "USD123.00", "123.00 US dollars"). All of these flavors can be
- * easily localized.
- *
- *
To obtain a {@link NumberFormat} for a specific locale (including the default
- * locale) call one of NumberFormat
's factory methods such as {@link
- * NumberFormat#getInstance}. Do not call the DecimalFormat
constructors
- * directly, unless you know what you are doing, since the {@link NumberFormat} factory
- * methods may return subclasses other than DecimalFormat
. If you need to
- * customize the format object, do something like this:
- *
- *
- * - *- * NumberFormat f = NumberFormat.getInstance(loc); - * if (f instanceof DecimalFormat) { - * ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true); - * }
Example Usage - * - * Print out a number using the localized number, currency, and percent - * format for each locale. - * - *
- * - *- * Locale[] locales = NumberFormat.getAvailableLocales(); - * double myNumber = -1234.56; - * NumberFormat format; - * for (int j=0; j<3; ++j) { - * System.out.println("FORMAT"); - * for (int i = 0; i < locales.length; ++i) { - * if (locales[i].getCountry().length() == 0) { - * // Skip language-only locales - * continue; - * } - * System.out.print(locales[i].getDisplayName()); - * switch (j) { - * case 0: - * format = NumberFormat.getInstance(locales[i]); break; - * case 1: - * format = NumberFormat.getCurrencyInstance(locales[i]); break; - * default: - * format = NumberFormat.getPercentInstance(locales[i]); break; - * } - * try { - * // Assume format is a DecimalFormat - * System.out.print(": " + ((DecimalFormat) format).toPattern() - * + " -> " + form.format(myNumber)); - * } catch (Exception e) {} - * try { - * System.out.println(" -> " + format.parse(form.format(myNumber))); - * } catch (ParseException e) {} - * } - * }
Another example use getInstance(style).
- * Print out a number using the localized number, currency, percent,
- * scientific, integer, iso currency, and plural currency format for each locale.
- *
- *
- * - *- * ULocale locale = new ULocale("en_US"); - * double myNumber = 1234.56; - * for (int j=NumberFormat.NUMBERSTYLE; j<=NumberFormat.PLURALCURRENCYSTYLE; ++j) { - * NumberFormat format = NumberFormat.getInstance(locale, j); - * try { - * // Assume format is a DecimalFormat - * System.out.print(": " + ((DecimalFormat) format).toPattern() - * + " -> " + form.format(myNumber)); - * } catch (Exception e) {} - * try { - * System.out.println(" -> " + format.parse(form.format(myNumber))); - * } catch (ParseException e) {} - * }
A DecimalFormat
consists of a pattern and a set of
- * symbols. The pattern may be set directly using {@link #applyPattern}, or
- * indirectly using other API methods which manipulate aspects of the pattern, such as the
- * minimum number of integer digits. The symbols are stored in a {@link
- * DecimalFormatSymbols} object. When using the {@link NumberFormat} factory methods, the
- * pattern and symbols are read from ICU's locale data.
- *
- *
Many characters in a pattern are taken literally; they are matched during parsing - * and output unchanged during formatting. Special characters, on the other hand, stand - * for other characters, strings, or classes of characters. For example, the '#' - * character is replaced by a localized digit. Often the replacement character is the - * same as the pattern character; in the U.S. locale, the ',' grouping character is - * replaced by ','. However, the replacement is still happening, and if the symbols are - * modified, the grouping character changes. Some special characters affect the behavior - * of the formatter by their presence; for example, if the percent character is seen, then - * the value is multiplied by 100 before being displayed. - * - *
To insert a special character in a pattern as a literal, that is, without any - * special meaning, the character must be quoted. There are some exceptions to this which - * are noted below. - * - *
The characters listed here are used in non-localized patterns. Localized patterns - * use the corresponding characters taken from this formatter's {@link - * DecimalFormatSymbols} object instead, and these characters lose their special status. - * Two exceptions are the currency sign and quote, which are not localized. - * - *
- *- * - *- *
- *- * Symbol - * Location - * Localized? - * Meaning - * - * 0
- *Number - * Yes - * Digit - * - * 1-9
- *Number - * Yes - * '1' through '9' indicate rounding. - * - * @
- *Number - * No - * Significant digit - * - * #
- *Number - * Yes - * Digit, zero shows as absent - * - * .
- *Number - * Yes - * Decimal separator or monetary decimal separator - * - * -
- *Number - * Yes - * Minus sign - * - * ,
- *Number - * Yes - * Grouping separator - * - * E
- *Number - * Yes - * Separates mantissa and exponent in scientific notation. - * Need not be quoted in prefix or suffix. - * - * +
- *Exponent - * Yes - * Prefix positive exponents with localized plus sign. - * Need not be quoted in prefix or suffix. - * - * ;
- *Subpattern boundary - * Yes - * Separates positive and negative subpatterns - * - * %
- *Prefix or suffix - * Yes - * Multiply by 100 and show as percentage - * - * \u2030
- *Prefix or suffix - * Yes - * Multiply by 1000 and show as per mille - * - * ¤
(\u00A4
) - *Prefix or suffix - * No - * Currency sign, replaced by currency symbol. If - * doubled, replaced by international currency symbol. - * If tripled, replaced by currency plural names, for example, - * "US dollar" or "US dollars" for America. - * If present in a pattern, the monetary decimal separator - * is used instead of the decimal separator. - * - * '
- *Prefix or suffix - * No - * Used to quote special characters in a prefix or suffix, - * for example, "'#'#"
formats 123 to - *"#123"
. To create a single quote - * itself, use two in a row:"# o''clock"
. - *- * *
- *Prefix or suffix boundary - * Yes - * Pad escape, precedes pad character - *
A DecimalFormat
pattern contains a postive and negative subpattern, for
- * example, "#,##0.00;(#,##0.00)". Each subpattern has a prefix, a numeric part, and a
- * suffix. If there is no explicit negative subpattern, the negative subpattern is the
- * localized minus sign prefixed to the positive subpattern. That is, "0.00" alone is
- * equivalent to "0.00;-0.00". If there is an explicit negative subpattern, it serves
- * only to specify the negative prefix and suffix; the number of digits, minimal digits,
- * and other characteristics are ignored in the negative subpattern. That means that
- * "#,##0.0#;(#)" has precisely the same result as "#,##0.0#;(#,##0.0#)".
- *
- *
The prefixes, suffixes, and various symbols used for infinity, digits, thousands - * separators, decimal separators, etc. may be set to arbitrary values, and they will - * appear properly during formatting. However, care must be taken that the symbols and - * strings do not conflict, or parsing will be unreliable. For example, either the - * positive and negative prefixes or the suffixes must be distinct for {@link #parse} to - * be able to distinguish positive from negative values. Another example is that the - * decimal separator and thousands separator should be distinct characters, or parsing - * will be impossible. - * - *
The grouping separator is a character that separates clusters of integer - * digits to make large numbers more legible. It commonly used for thousands, but in some - * locales it separates ten-thousands. The grouping size is the number of digits - * between the grouping separators, such as 3 for "100,000,000" or 4 for "1 0000 - * 0000". There are actually two different grouping sizes: One used for the least - * significant integer digits, the primary grouping size, and one used for all - * others, the secondary grouping size. In most locales these are the same, but - * sometimes they are different. For example, if the primary grouping interval is 3, and - * the secondary is 2, then this corresponds to the pattern "#,##,##0", and the number - * 123456789 is formatted as "12,34,56,789". If a pattern contains multiple grouping - * separators, the interval between the last one and the end of the integer defines the - * primary grouping size, and the interval between the last two defines the secondary - * grouping size. All others are ignored, so "#,##,###,####" == "###,###,####" == - * "##,#,###,####". - * - *
Illegal patterns, such as "#.#.#" or "#.###,###", will cause
- * DecimalFormat
to throw an {@link IllegalArgumentException} with a message
- * that describes the problem.
- *
- *
- * pattern := subpattern (';' subpattern)? - * subpattern := prefix? number exponent? suffix? - * number := (integer ('.' fraction)?) | sigDigits - * prefix := '\u0000'..'\uFFFD' - specialCharacters - * suffix := '\u0000'..'\uFFFD' - specialCharacters - * integer := '#'* '0'* '0' - * fraction := '0'* '#'* - * sigDigits := '#'* '@' '@'* '#'* - * exponent := 'E' '+'? '0'* '0' - * padSpec := '*' padChar - * padChar := '\u0000'..'\uFFFD' - quote - * - * Notation: - * X* 0 or more instances of X - * X? 0 or 1 instances of X - * X|Y either X or Y - * C..D any character from C up to D, inclusive - * S-T characters in S, except those in T - *- * The first subpattern is for positive numbers. The second (optional) - * subpattern is for negative numbers. - * - *
Not indicated in the BNF syntax above: - * - *
padSpec
may appear before the prefix, after the
- * prefix, before the suffix, after the suffix, or not at all.
- *
- * DecimalFormat
parses all Unicode characters that represent decimal
- * digits, as defined by {@link UCharacter#digit}. In addition,
- * DecimalFormat
also recognizes as digits the ten consecutive characters
- * starting with the localized zero digit defined in the {@link DecimalFormatSymbols}
- * object. During formatting, the {@link DecimalFormatSymbols}-based digits are output.
- *
- *
During parsing, grouping separators are ignored. - * - *
For currency parsing, the formatter is able to parse every currency style formats no - * matter which style the formatter is constructed with. For example, a formatter - * instance gotten from NumberFormat.getInstance(ULocale, NumberFormat.CURRENCYSTYLE) can - * parse formats such as "USD1.00" and "3.00 US dollars". - * - *
If {@link #parse(String, ParsePosition)} fails to parse a string, it returns
- * null
and leaves the parse position unchanged. The convenience method
- * {@link #parse(String)} indicates parse failure by throwing a {@link
- * java.text.ParseException}.
- *
- *
Parsing an extremely large or small absolute value (such as 1.0E10000 or 1.0E-10000)
- * requires huge memory allocation for representing the parsed number. Such input may expose
- * a risk of DoS attacks. To prevent huge memory allocation triggered by such inputs,
- * DecimalFormat
internally limits of maximum decimal digits to be 1000. Thus,
- * an input string resulting more than 1000 digits in plain decimal representation (non-exponent)
- * will be treated as either overflow (positive/negative infinite) or underflow (+0.0/-0.0).
- *
- *
Formatting is guided by several parameters, all of which can be specified either - * using a pattern or using the API. The following description applies to formats that do - * not use scientific notation or significant - * digits. - * - *
Special Values - * - *
NaN
is represented as a single character, typically
- * \uFFFD
. This character is determined by the {@link
- * DecimalFormatSymbols} object. This is the only value for which the prefixes and
- * suffixes are not used.
- *
- *
Infinity is represented as a single character, typically \u221E
,
- * with the positive or negative prefixes and suffixes applied. The infinity character is
- * determined by the {@link DecimalFormatSymbols} object.
- *
- *
Numbers in scientific notation are expressed as the product of a mantissa and a
- * power of ten, for example, 1234 can be expressed as 1.234 x 103. The
- * mantissa is typically in the half-open interval [1.0, 10.0) or sometimes [0.0, 1.0),
- * but it need not be. DecimalFormat
supports arbitrary mantissas.
- * DecimalFormat
can be instructed to use scientific notation through the API
- * or through the pattern. In a pattern, the exponent character immediately followed by
- * one or more digit characters indicates scientific notation. Example: "0.###E0" formats
- * the number 1234 as "1.234E3".
- *
- *
DecimalFormat
has two ways of controlling how many digits are shows: (a)
- * significant digits counts, or (b) integer and fraction digit counts. Integer and
- * fraction digit counts are described above. When a formatter is using significant
- * digits counts, the number of integer and fraction digits is not specified directly, and
- * the formatter settings for these counts are ignored. Instead, the formatter uses
- * however many integer and fraction digits are required to display the specified number
- * of significant digits. Examples:
- *
- * - *- * - *- *
- *- * Pattern - * Minimum significant digits - * Maximum significant digits - * Number - * Output of format() - * - * @@@
- *3 - * 3 - * 12345 - * 12300
- *- * @@@
- *3 - * 3 - * 0.12345 - * 0.123
- *- * @@##
- *2 - * 4 - * 3.14159 - * 3.142
- *- * @@##
- *2 - * 4 - * 1.23004 - * 1.23
- *
'@'
and
- * '#'
characters. The minimum number of significant digits is the number of
- * '@'
characters. The maximum number of significant digits is the number of
- * '@'
characters plus the number of '#'
characters following on
- * the right. For example, the pattern "@@@"
indicates exactly 3 significant
- * digits. The pattern "@##"
indicates from 1 to 3 significant digits.
- * Trailing zero digits to the right of the decimal separator are suppressed after the
- * minimum number of significant digits have been shown. For example, the pattern
- * "@##"
formats the number 0.1203 as "0.12"
.
- *
- * '0'
pattern character. Patterns such as "@00"
or
- * "@.###"
are disallowed.
- *
- * '#'
characters may be prepended to the left of the
- * leftmost '@'
character. These have no effect on the minimum and maximum
- * significant digits counts, but may be used to position grouping separators. For
- * example, "#,#@#"
indicates a minimum of one significant digits, a maximum
- * of two significant digits, and a grouping size of three.
- *
- * '@'
pattern character. Alternatively, call {@link
- * #setSignificantDigitsUsed setSignificantDigitsUsed(true)}.
- *
- * '@'
pattern character. Alternatively, call {@link
- * #setSignificantDigitsUsed setSignificantDigitsUsed(false)}.
- *
- * getMinimumSignificantDigits() -
- * 1
, and a maximum fraction digit count of getMaximumSignificantDigits() -
- * 1
. For example, the pattern "@@###E0"
is equivalent to
- * "0.0###E0"
.
- *
- * DecimalFormat
supports padding the result of {@link #format} to a
- * specific width. Padding may be specified either through the API or through the pattern
- * syntax. In a pattern the pad escape character, followed by a single pad character,
- * causes padding to be parsed and formatted. The pad escape character is '*' in
- * unlocalized patterns, and can be localized using {@link
- * DecimalFormatSymbols#setPadEscape}. For example, "$*x#,##0.00"
formats
- * 123 to "$xx123.00"
, and 1234 to "$1,234.00"
.
- *
- *
"* #0
- * o''clock"
, the format width is 10.
- *
- * char
s).
- *
- * char
immediately following the
- * pad escape is the pad character. This may be any character, including a special pattern
- * character. That is, the pad escape escapes the following character. If there
- * is no character after the pad escape, then the pattern is illegal.
- *
- * - * Rounding - * - *
DecimalFormat
supports rounding to a specific increment. For example,
- * 1230 rounded to the nearest 50 is 1250. 1.234 rounded to the nearest 0.65 is 1.3. The
- * rounding increment may be specified through the API or in a pattern. To specify a
- * rounding increment in a pattern, include the increment in the pattern itself. "#,#50"
- * specifies a rounding increment of 50. "#,##0.05" specifies a rounding increment of
- * 0.05.
- *
- *
DecimalFormat
objects are not synchronized. Multiple threads should
- * not access one formatter concurrently.
- *
- * @see java.text.Format
- * @see NumberFormat
- * @author Mark Davis
- * @author Alan Liu
- * @stable ICU 2.0
- */
+/** @stable ICU 2.0 */
public class DecimalFormat extends NumberFormat {
- /**
- * Creates a DecimalFormat using the default pattern and symbols for the default
- * FORMAT
locale. This is a convenient way to obtain a DecimalFormat when
- * internationalization is not the main concern.
- *
- *
To obtain standard formats for a given locale, use the factory methods on
- * NumberFormat such as getNumberInstance. These factories will return the most
- * appropriate sub-class of NumberFormat for a given locale.
- *
- * @see NumberFormat#getInstance
- * @see NumberFormat#getNumberInstance
- * @see NumberFormat#getCurrencyInstance
- * @see NumberFormat#getPercentInstance
- * @see Category#FORMAT
- * @stable ICU 2.0
- */
- public DecimalFormat() {
- ULocale def = ULocale.getDefault(Category.FORMAT);
- String pattern = getPattern(def, 0);
- // Always applyPattern after the symbols are set
- this.symbols = new DecimalFormatSymbols(def);
- setCurrency(Currency.getInstance(def));
- applyPatternWithoutExpandAffix(pattern, false);
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- currencyPluralInfo = new CurrencyPluralInfo(def);
- // the exact pattern is not known until the plural count is known.
- // so, no need to expand affix now.
- } else {
- expandAffixAdjustWidth(null);
- }
- }
+ /** New serialization in ICU 59: declare different version from ICU 58. */
+ private static final long serialVersionUID = 864413376551465018L;
- /**
- * Creates a DecimalFormat from the given pattern and the symbols for the default
- * FORMAT
locale. This is a convenient way to obtain a DecimalFormat when
- * internationalization is not the main concern.
- *
- *
To obtain standard formats for a given locale, use the factory methods on - * NumberFormat such as getNumberInstance. These factories will return the most - * appropriate sub-class of NumberFormat for a given locale. - * - * @param pattern A non-localized pattern string. - * @throws IllegalArgumentException if the given pattern is invalid. - * @see NumberFormat#getInstance - * @see NumberFormat#getNumberInstance - * @see NumberFormat#getCurrencyInstance - * @see NumberFormat#getPercentInstance - * @see Category#FORMAT - * @stable ICU 2.0 - */ - public DecimalFormat(String pattern) { - // Always applyPattern after the symbols are set - ULocale def = ULocale.getDefault(Category.FORMAT); - this.symbols = new DecimalFormatSymbols(def); - setCurrency(Currency.getInstance(def)); - applyPatternWithoutExpandAffix(pattern, false); - if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) { - currencyPluralInfo = new CurrencyPluralInfo(def); - } else { - expandAffixAdjustWidth(null); - } - } + /** + * One non-transient field such that deserialization can determine the version of the class. This + * field has existed since the very earliest versions of DecimalFormat. + */ + @SuppressWarnings("unused") + private final int serialVersionOnStream = 5; - /** - * Creates a DecimalFormat from the given pattern and symbols. Use this constructor - * when you need to completely customize the behavior of the format. - * - *
To obtain standard formats for a given locale, use the factory methods on - * NumberFormat such as getInstance or getCurrencyInstance. If you need only minor - * adjustments to a standard format, you can modify the format returned by a - * NumberFormat factory method. - * - * @param pattern a non-localized pattern string - * @param symbols the set of symbols to be used - * @exception IllegalArgumentException if the given pattern is invalid - * @see NumberFormat#getInstance - * @see NumberFormat#getNumberInstance - * @see NumberFormat#getCurrencyInstance - * @see NumberFormat#getPercentInstance - * @see DecimalFormatSymbols - * @stable ICU 2.0 - */ - public DecimalFormat(String pattern, DecimalFormatSymbols symbols) { - createFromPatternAndSymbols(pattern, symbols); - } + //=====================================================================================// + // INSTANCE FIELDS // + //=====================================================================================// - private void createFromPatternAndSymbols(String pattern, DecimalFormatSymbols inputSymbols) { - // Always applyPattern after the symbols are set - symbols = (DecimalFormatSymbols) inputSymbols.clone(); - if (pattern.indexOf(CURRENCY_SIGN) >= 0) { - // Only spend time with currency symbols when we're going to display it. - // Also set some defaults before the apply pattern. - setCurrencyForSymbols(); - } - applyPatternWithoutExpandAffix(pattern, false); - if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) { - currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale()); - } else { - expandAffixAdjustWidth(null); - } - } + // Fields are package-private, so that subclasses can use them. + // properties should be final, but clone won't work if we make it final. + // All fields are transient because custom serialization is used. - /** - * Creates a DecimalFormat from the given pattern, symbols, information used for - * currency plural format, and format style. Use this constructor when you need to - * completely customize the behavior of the format. - * - *
To obtain standard formats for a given locale, use the factory methods on - * NumberFormat such as getInstance or getCurrencyInstance. - * - *
If you need only minor adjustments to a standard format, you can modify the - * format returned by a NumberFormat factory method using the setters. - * - *
If you want to completely customize a decimal format, using your own - * DecimalFormatSymbols (such as group separators) and your own information for - * currency plural formatting (such as plural rule and currency plural patterns), you - * can use this constructor. - * - * @param pattern a non-localized pattern string - * @param symbols the set of symbols to be used - * @param infoInput the information used for currency plural format, including - * currency plural patterns and plural rules. - * @param style the decimal formatting style, it is one of the following values: - * NumberFormat.NUMBERSTYLE; NumberFormat.CURRENCYSTYLE; NumberFormat.PERCENTSTYLE; - * NumberFormat.SCIENTIFICSTYLE; NumberFormat.INTEGERSTYLE; - * NumberFormat.ISOCURRENCYSTYLE; NumberFormat.PLURALCURRENCYSTYLE; - * @stable ICU 4.2 - */ - public DecimalFormat(String pattern, DecimalFormatSymbols symbols, CurrencyPluralInfo infoInput, - int style) { - CurrencyPluralInfo info = infoInput; - if (style == NumberFormat.PLURALCURRENCYSTYLE) { - info = (CurrencyPluralInfo) infoInput.clone(); - } - create(pattern, symbols, info, style); - } + /** + * The property bag corresponding to user-specified settings and settings from the pattern string. + * In principle this should be final, but serialize and clone won't work if it is final. Does not + * need to be volatile because the reference never changes. + */ + /* final */ transient Properties properties; - private void create(String pattern, DecimalFormatSymbols inputSymbols, CurrencyPluralInfo info, - int inputStyle) { - if (inputStyle != NumberFormat.PLURALCURRENCYSTYLE) { - createFromPatternAndSymbols(pattern, inputSymbols); - } else { - // Always applyPattern after the symbols are set - symbols = (DecimalFormatSymbols) inputSymbols.clone(); - currencyPluralInfo = info; - // the pattern used in format is not fixed until formatting, in which, the - // number is known and will be used to pick the right pattern based on plural - // count. Here, set the pattern as the pattern of plural count == "other". - // For most locale, the patterns are probably the same for all plural - // count. If not, the right pattern need to be re-applied during format. - String currencyPluralPatternForOther = - currencyPluralInfo.getCurrencyPluralPattern("other"); - applyPatternWithoutExpandAffix(currencyPluralPatternForOther, false); - setCurrencyForSymbols(); - } - style = inputStyle; - } + /** + * The symbols for the current locale. Volatile because threads may read and write at the same + * time. + */ + transient volatile DecimalFormatSymbols symbols; - /** - * Creates a DecimalFormat for currency plural format from the given pattern, symbols, - * and style. - */ - DecimalFormat(String pattern, DecimalFormatSymbols inputSymbols, int style) { - CurrencyPluralInfo info = null; - if (style == NumberFormat.PLURALCURRENCYSTYLE) { - info = new CurrencyPluralInfo(inputSymbols.getULocale()); - } - create(pattern, inputSymbols, info, style); - } + /** + * The pre-computed formatter object. Setters cause this to be re-computed atomically. The {@link + * #format} method uses the formatter directly without needing to synchronize. Volatile because + * threads may read and write at the same time. + */ + transient volatile SingularFormat formatter; - /** - * {@inheritDoc} - * @stable ICU 2.0 - */ - @Override - public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) { - return format(number, result, fieldPosition, false); - } + /** + * The effective properties as exported from the formatter object. Volatile because threads may + * read and write at the same time. + */ + transient volatile Properties exportedProperties; - // See if number is negative. - // usage: isNegative(multiply(numberToBeFormatted)); - private boolean isNegative(double number) { - // Detecting whether a double is negative is easy with the exception of the value - // -0.0. This is a double which has a zero mantissa (and exponent), but a negative - // sign bit. It is semantically distinct from a zero with a positive sign bit, and - // this distinction is important to certain kinds of computations. However, it's a - // little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you - // may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) == - // -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by - // bugs 4106658, 4106667, and 4147706. Liu 7/6/98. - return (number < 0.0) || (number == 0.0 && 1 / number < 0.0); - } + //=====================================================================================// + // CONSTRUCTORS // + //=====================================================================================// - // Rounds the number and strips of the negative sign. - // usage: round(multiply(numberToBeFormatted)) - private double round(double number) { - boolean isNegative = isNegative(number); - if (isNegative) - number = -number; + /** @stable ICU 2.0 */ + public DecimalFormat() { + // Use the locale's default pattern + ULocale def = ULocale.getDefault(ULocale.Category.FORMAT); + String pattern = getPattern(def, 0); + symbols = getDefaultSymbols(); + properties = new Properties(); + exportedProperties = new Properties(); + // Regression: ignore pattern rounding information if the pattern has currency symbols. + boolean ignorePatternRounding = AffixPatternUtils.hasCurrencySymbols(pattern); + setPropertiesFromPattern(pattern, ignorePatternRounding); + refreshFormatter(); + } - // Apply rounding after multiplier - if (roundingDouble > 0.0) { - // number = roundingDouble - // * round(number / roundingDouble, roundingMode, isNegative); - return round( - number, roundingDouble, roundingDoubleReciprocal, roundingMode, - isNegative); - } - return number; - } + /** @stable ICU 2.0 */ + public DecimalFormat(String pattern) { + symbols = getDefaultSymbols(); + properties = new Properties(); + exportedProperties = new Properties(); + // Regression: ignore pattern rounding information if the pattern has currency symbols. + boolean ignorePatternRounding = AffixPatternUtils.hasCurrencySymbols(pattern); + setPropertiesFromPattern(pattern, ignorePatternRounding); + refreshFormatter(); + } - // Multiplies given number by multipler (if there is one) returning the new - // number. If there is no multiplier, returns the number passed in unchanged. - private double multiply(double number) { - if (multiplier != 1) { - return number * multiplier; - } - return number; - } + /** @stable ICU 2.0 */ + public DecimalFormat(String pattern, DecimalFormatSymbols symbols) { + this.symbols = (DecimalFormatSymbols) symbols.clone(); + properties = new Properties(); + exportedProperties = new Properties(); + // Regression: ignore pattern rounding information if the pattern has currency symbols. + boolean ignorePatternRounding = AffixPatternUtils.hasCurrencySymbols(pattern); + setPropertiesFromPattern(pattern, ignorePatternRounding); + refreshFormatter(); + } - // [Spark/CDL] The actual method to format number. If boolean value - // parseAttr == true, then attribute information will be recorded. - private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition, - boolean parseAttr) { - fieldPosition.setBeginIndex(0); - fieldPosition.setEndIndex(0); + /** @stable ICU 4.2 */ + public DecimalFormat( + String pattern, DecimalFormatSymbols symbols, CurrencyPluralInfo infoInput, int style) { + this.symbols = (DecimalFormatSymbols) symbols.clone(); + properties = new Properties(); + exportedProperties = new Properties(); + properties.setCurrencyPluralInfo(infoInput); + refreshFormatter(); + } - if (Double.isNaN(number)) { - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - fieldPosition.setBeginIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setBeginIndex(result.length()); - } - - result.append(symbols.getNaN()); - // TODO: Combine setting a single FieldPosition or adding to an AttributedCharacterIterator - // into a function like recordAttribute(FieldAttribute, begin, end). - - // [Spark/CDL] Add attribute for NaN here. - // result.append(symbols.getNaN()); - if (parseAttr) { - addAttribute(Field.INTEGER, result.length() - symbols.getNaN().length(), - result.length()); - } - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - fieldPosition.setEndIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setEndIndex(result.length()); - } - - addPadding(result, fieldPosition, 0, 0); - return result; - } - - // Do this BEFORE checking to see if value is negative or infinite and - // before rounding. - number = multiply(number); - boolean isNegative = isNegative(number); - number = round(number); - - if (Double.isInfinite(number)) { - int prefixLen = appendAffix(result, isNegative, true, fieldPosition, parseAttr); - - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - fieldPosition.setBeginIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setBeginIndex(result.length()); - } - - // [Spark/CDL] Add attribute for infinity here. - result.append(symbols.getInfinity()); - if (parseAttr) { - addAttribute(Field.INTEGER, result.length() - symbols.getInfinity().length(), - result.length()); - } - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - fieldPosition.setEndIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setEndIndex(result.length()); - } - - int suffixLen = appendAffix(result, isNegative, false, fieldPosition, parseAttr); - - addPadding(result, fieldPosition, prefixLen, suffixLen); - return result; - } - - int precision = precision(false); - - // This is to fix rounding for scientific notation. See ticket:10542. - // This code should go away when a permanent fix is done for ticket:9931. - // - // This block of code only executes for scientific notation so it will not interfere with the - // previous fix in {@link #resetActualRounding} for fixed decimal numbers. - // Moreover this code only runs when there is rounding to be done (precision > 0) and when the - // rounding mode is something other than ROUND_HALF_EVEN. - // This block of code does the correct rounding of number in advance so that it will fit into - // the number of digits indicated by precision. In this way, we avoid using the default - // ROUND_HALF_EVEN behavior of DigitList. For example, if number = 0.003016 and roundingMode = - // ROUND_DOWN and precision = 3 then after this code executes, number = 0.00301 (3 significant digits) - if (useExponentialNotation && precision > 0 && number != 0.0 && roundingMode != BigDecimal.ROUND_HALF_EVEN) { - int log10RoundingIncr = 1 - precision + (int) Math.floor(Math.log10(Math.abs(number))); - double roundingIncReciprocal = 0.0; - double roundingInc = 0.0; - if (log10RoundingIncr < 0) { - roundingIncReciprocal = - BigDecimal.ONE.movePointRight(-log10RoundingIncr).doubleValue(); - } else { - roundingInc = - BigDecimal.ONE.movePointRight(log10RoundingIncr).doubleValue(); - } - number = DecimalFormat.round(number, roundingInc, roundingIncReciprocal, roundingMode, isNegative); - } - // End fix for ticket:10542 - - // At this point we are guaranteed a nonnegative finite - // number. - synchronized (digitList) { - digitList.set(number, precision, !useExponentialNotation && - !areSignificantDigitsUsed()); - return subformat(number, result, fieldPosition, isNegative, false, parseAttr); - } + /** Package-private constructor used by NumberFormat. */ + DecimalFormat(String pattern, DecimalFormatSymbols symbols, int choice) { + this.symbols = (DecimalFormatSymbols) symbols.clone(); + properties = new Properties(); + exportedProperties = new Properties(); + // If choice is a currency type, ignore the rounding information. + if (choice == CURRENCYSTYLE + || choice == ISOCURRENCYSTYLE + || choice == ACCOUNTINGCURRENCYSTYLE + || choice == CASHCURRENCYSTYLE + || choice == STANDARDCURRENCYSTYLE + || choice == PLURALCURRENCYSTYLE + || AffixPatternUtils.hasCurrencySymbols(pattern)) { + setPropertiesFromPattern(pattern, true); + } else { + setPropertiesFromPattern(pattern, false); } + refreshFormatter(); + } - /** - * This is a special function used by the CompactDecimalFormat subclass. - * It completes only the rounding portion of the formatting and returns - * the resulting double. CompactDecimalFormat uses the result to compute - * the plural form to use. - * - * @param number The number to format. - * @return The number rounded to the correct number of significant digits - * with negative sign stripped off. - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - double adjustNumberAsInFormatting(double number) { - if (Double.isNaN(number)) { - return number; - } - number = round(multiply(number)); - if (Double.isInfinite(number)) { - return number; - } - return toDigitList(number).getDouble(); - } + private static DecimalFormatSymbols getDefaultSymbols() { + return DecimalFormatSymbols.getInstance(); + } - @Deprecated - DigitList toDigitList(double number) { - DigitList result = new DigitList(); - result.set(number, precision(false), false); - return result; - } + /** + * Parses the given pattern string and overwrites the settings specified in the pattern string. + * The properties corresponding to the following setters are overwritten, either with their + * default values or with the value specified in the pattern string: + * + *
For more information on pattern strings, see UTS #35. + * + * @stable ICU 2.0 + */ + public synchronized void applyPattern(String pattern) { + setPropertiesFromPattern(pattern, false); + // Backwards compatibility: clear out user-specified prefix and suffix, + // as well as CurrencyPluralInfo. + properties.setPositivePrefix(null); + properties.setNegativePrefix(null); + properties.setPositiveSuffix(null); + properties.setNegativeSuffix(null); + properties.setCurrencyPluralInfo(null); + refreshFormatter(); + } - /** - * This is a special function used by the CompactDecimalFormat subclass - * to determine if the number to be formatted is negative. - * - * @param number The number to format. - * @return True if number is negative. - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - boolean isNumberNegative(double number) { - if (Double.isNaN(number)) { - return false; - } - return isNegative(multiply(number)); - } + /** + * Converts the given string to standard notation and then parses it using {@link #applyPattern}. + * + *
Localized notation means that instead of using generic placeholders in the pattern, you use
+ * the corresponding locale-specific characters instead. For example, in locale fr-FR,
+ * the period in the pattern "0.000" means "decimal" in standard notation (as it does in every
+ * other locale), but it means "grouping" in localized notation.
+ *
+ * @param localizedPattern The pattern string in localized notation.
+ * @stable ICU 2.0
+ */
+ public synchronized void applyLocalizedPattern(String localizedPattern) {
+ String pattern = PatternString.convertLocalized(localizedPattern, symbols, false);
+ applyPattern(pattern);
+ }
- /**
- * Round a double value to the nearest multiple of the given rounding increment,
- * according to the given mode. This is equivalent to rounding value/roundingInc to
- * the nearest integer, according to the given mode, and returning that integer *
- * roundingInc. Note this is changed from the version in 2.4, since division of
- * doubles have inaccuracies. jitterbug 1871.
- *
- * @param number
- * the absolute value of the number to be rounded
- * @param roundingInc
- * the rounding increment
- * @param roundingIncReciprocal
- * if non-zero, is the reciprocal of rounding inc.
- * @param mode
- * a BigDecimal rounding mode
- * @param isNegative
- * true if the number to be rounded is negative
- * @return the absolute value of the rounded result
- */
- private static double round(double number, double roundingInc, double roundingIncReciprocal,
- int mode, boolean isNegative) {
+ //=====================================================================================//
+ // CLONE AND SERIALIZE //
+ //=====================================================================================//
- double div = roundingIncReciprocal == 0.0 ? number / roundingInc : number *
- roundingIncReciprocal;
+ /** @stable ICU 2.0 */
+ @Override
+ public Object clone() {
+ DecimalFormat other = (DecimalFormat) super.clone();
+ other.symbols = (DecimalFormatSymbols) symbols.clone();
+ other.properties = properties.clone();
+ other.exportedProperties = new Properties();
+ other.refreshFormatter();
+ return other;
+ }
- // do the absolute cases first
+ /**
+ * Custom serialization: save property bag and symbols; the formatter object can be re-created
+ * from just that amount of information.
+ */
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ // ICU 59 custom serialization.
+ // Write class metadata and serialVersionOnStream field:
+ oos.defaultWriteObject();
+ // Extra int for possible future use:
+ oos.writeInt(0);
+ // 1) Property Bag
+ oos.writeObject(properties);
+ // 2) DecimalFormatSymbols
+ oos.writeObject(symbols);
+ }
- switch (mode) {
- case BigDecimal.ROUND_CEILING:
- div = (isNegative ? Math.floor(div + epsilon) : Math.ceil(div - epsilon));
- break;
- case BigDecimal.ROUND_FLOOR:
- div = (isNegative ? Math.ceil(div - epsilon) : Math.floor(div + epsilon));
- break;
- case BigDecimal.ROUND_DOWN:
- div = (Math.floor(div + epsilon));
- break;
- case BigDecimal.ROUND_UP:
- div = (Math.ceil(div - epsilon));
- break;
- case BigDecimal.ROUND_UNNECESSARY:
- if (div != Math.floor(div)) {
- throw new ArithmeticException("Rounding necessary");
- }
- return number;
- default:
+ /**
+ * Custom serialization: re-create object from serialized property bag and symbols. Also supports
+ * reading from the legacy (pre-ICU4J 59) format and converting it to the new form.
+ */
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ ObjectInputStream.GetField fieldGetter = ois.readFields();
+ ObjectStreamField[] serializedFields = fieldGetter.getObjectStreamClass().getFields();
+ int serialVersion = fieldGetter.get("serialVersionOnStream", -1);
- // Handle complex cases, where the choice depends on the closer value.
-
- // We figure out the distances to the two possible values, ceiling and floor.
- // We then go for the diff that is smaller. Only if they are equal does the
- // mode matter.
-
- double ceil = Math.ceil(div);
- double ceildiff = ceil - div; // (ceil * roundingInc) - number;
- double floor = Math.floor(div);
- double floordiff = div - floor; // number - (floor * roundingInc);
-
- // Note that the diff values were those mapped back to the "normal" space by
- // using the roundingInc. I don't have access to the original author of the
- // code but suspect that that was to produce better result in edge cases
- // because of machine precision, rather than simply using the difference
- // between, say, ceil and div. However, it didn't work in all cases. Am
- // trying instead using an epsilon value.
-
- switch (mode) {
- case BigDecimal.ROUND_HALF_EVEN:
- // We should be able to just return Math.rint(a), but this
- // doesn't work in some VMs.
- // if one is smaller than the other, take the corresponding side
- if (floordiff + epsilon < ceildiff) {
- div = floor;
- } else if (ceildiff + epsilon < floordiff) {
- div = ceil;
- } else { // they are equal, so we want to round to whichever is even
- double testFloor = floor / 2;
- div = (testFloor == Math.floor(testFloor)) ? floor : ceil;
- }
- break;
- case BigDecimal.ROUND_HALF_DOWN:
- div = ((floordiff <= ceildiff + epsilon) ? floor : ceil);
- break;
- case BigDecimal.ROUND_HALF_UP:
- div = ((ceildiff <= floordiff + epsilon) ? ceil : floor);
- break;
- default:
- throw new IllegalArgumentException("Invalid rounding mode: " + mode);
- }
- }
- number = roundingIncReciprocal == 0.0 ? div * roundingInc : div / roundingIncReciprocal;
- return number;
- }
-
- private static double epsilon = 0.00000000001;
-
- /**
- * @stable ICU 2.0
- */
- // [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean
- @Override
- public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- private StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition,
- boolean parseAttr) {
- fieldPosition.setBeginIndex(0);
- fieldPosition.setEndIndex(0);
-
- // If we are to do rounding, we need to move into the BigDecimal
- // domain in order to do divide/multiply correctly.
- if (actualRoundingIncrementICU != null) {
- return format(BigDecimal.valueOf(number), result, fieldPosition);
- }
-
- boolean isNegative = (number < 0);
- if (isNegative)
- number = -number;
-
- // In general, long values always represent real finite numbers, so we don't have
- // to check for +/- Infinity or NaN. However, there is one case we have to be
- // careful of: The multiplier can push a number near MIN_VALUE or MAX_VALUE
- // outside the legal range. We check for this before multiplying, and if it
- // happens we use BigInteger instead.
- if (multiplier != 1) {
- boolean tooBig = false;
- if (number < 0) { // This can only happen if number == Long.MIN_VALUE
- long cutoff = Long.MIN_VALUE / multiplier;
- tooBig = (number <= cutoff); // number == cutoff can only happen if multiplier == -1
- } else {
- long cutoff = Long.MAX_VALUE / multiplier;
- tooBig = (number > cutoff);
- }
- if (tooBig) {
- // [Spark/CDL] Use
- // format_BigInteger_StringBuffer_FieldPosition_boolean instead
- // parseAttr is used to judge whether to synthesize attributes.
- return format(BigInteger.valueOf(isNegative ? -number : number), result,
- fieldPosition, parseAttr);
- }
- }
-
- number *= multiplier;
- synchronized (digitList) {
- digitList.set(number, precision(true));
- // Issue 11808
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number, result, fieldPosition, isNegative, true, parseAttr);
- }
- }
-
- /**
- * Formats a BigInteger number.
- *
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(BigInteger number, StringBuffer result,
- FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- private StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition,
- boolean parseAttr) {
- // If we are to do rounding, we need to move into the BigDecimal
- // domain in order to do divide/multiply correctly.
- if (actualRoundingIncrementICU != null) {
- return format(new BigDecimal(number), result, fieldPosition);
- }
-
- if (multiplier != 1) {
- number = number.multiply(BigInteger.valueOf(multiplier));
- }
-
- // At this point we are guaranteed a nonnegative finite
- // number.
- synchronized (digitList) {
- digitList.set(number, precision(true));
- // For issue 11808.
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number.intValue(), result, fieldPosition, number.signum() < 0, true,
- parseAttr);
- }
- }
-
- /**
- * Formats a BigDecimal number.
- *
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
- FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- private StringBuffer format(java.math.BigDecimal number, StringBuffer result,
- FieldPosition fieldPosition,
- boolean parseAttr) {
- if (multiplier != 1) {
- number = number.multiply(java.math.BigDecimal.valueOf(multiplier));
- }
-
- if (actualRoundingIncrement != null) {
- number = number.divide(actualRoundingIncrement, 0, roundingMode).multiply(actualRoundingIncrement);
- }
-
- synchronized (digitList) {
- digitList.set(number, precision(false), !useExponentialNotation &&
- !areSignificantDigitsUsed());
- // For issue 11808.
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0,
- false, parseAttr);
- }
- }
-
- /**
- * Formats a BigDecimal number.
- *
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(BigDecimal number, StringBuffer result,
- FieldPosition fieldPosition) {
- // This method is just a copy of the corresponding java.math.BigDecimal method
- // for now. It isn't very efficient since it must create a conversion object to
- // do math on the rounding increment. In the future we may try to clean this up,
- // or even better, limit our support to just one flavor of BigDecimal.
- if (multiplier != 1) {
- number = number.multiply(BigDecimal.valueOf(multiplier), mathContext);
- }
-
- if (actualRoundingIncrementICU != null) {
- number = number.divide(actualRoundingIncrementICU, 0, roundingMode)
- .multiply(actualRoundingIncrementICU, mathContext);
- }
-
- synchronized (digitList) {
- digitList.set(number, precision(false), !useExponentialNotation &&
- !areSignificantDigitsUsed());
- // For issue 11808.
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0,
- false, false);
- }
- }
-
- /**
- * Returns true if a grouping separator belongs at the given position, based on whether
- * grouping is in use and the values of the primary and secondary grouping interval.
- *
- * @param pos the number of integer digits to the right of the current position. Zero
- * indicates the position after the rightmost integer digit.
- * @return true if a grouping character belongs at the current position.
- */
- private boolean isGroupingPosition(int pos) {
- boolean result = false;
- if (isGroupingUsed() && (pos > 0) && (groupingSize > 0)) {
- if ((groupingSize2 > 0) && (pos > groupingSize)) {
- result = ((pos - groupingSize) % groupingSize2) == 0;
- } else {
- result = pos % groupingSize == 0;
- }
- }
- return result;
- }
-
- /**
- * Return the number of fraction digits to display, or the total
- * number of digits for significant digit formats and exponential
- * formats.
- */
- private int precision(boolean isIntegral) {
- if (areSignificantDigitsUsed()) {
- return getMaximumSignificantDigits();
- } else if (useExponentialNotation) {
- return getMinimumIntegerDigits() + getMaximumFractionDigits();
+ if (serialVersion > 5) {
+ throw new IOException(
+ "Cannot deserialize newer com.ibm.icu.text.DecimalFormat (v" + serialVersion + ")");
+ } else if (serialVersion == 5) {
+ ///// ICU 59+ SERIALIZATION FORMAT /////
+ // We expect this field and no other fields:
+ if (serializedFields.length > 1) {
+ throw new IOException("Too many fields when reading serial version 5");
+ }
+ // Extra int for possible future use:
+ ois.readInt();
+ // 1) Property Bag
+ properties = (Properties) ois.readObject();
+ // 2) DecimalFormatSymbols
+ symbols = (DecimalFormatSymbols) ois.readObject();
+ // Re-build transient fields
+ exportedProperties = new Properties();
+ refreshFormatter();
+ } else {
+ ///// LEGACY SERIALIZATION FORMAT /////
+ properties = new Properties();
+ // Loop through the fields. Not all fields necessarily exist in the serialization.
+ String pp = null, ppp = null, ps = null, psp = null;
+ String np = null, npp = null, ns = null, nsp = null;
+ for (ObjectStreamField field : serializedFields) {
+ String name = field.getName();
+ if (name.equals("decimalSeparatorAlwaysShown")) {
+ setDecimalSeparatorAlwaysShown(fieldGetter.get("decimalSeparatorAlwaysShown", false));
+ } else if (name.equals("exponentSignAlwaysShown")) {
+ setExponentSignAlwaysShown(fieldGetter.get("exponentSignAlwaysShown", false));
+ } else if (name.equals("formatWidth")) {
+ setFormatWidth(fieldGetter.get("formatWidth", 0));
+ } else if (name.equals("groupingSize")) {
+ setGroupingSize(fieldGetter.get("groupingSize", (byte) 3));
+ } else if (name.equals("groupingSize2")) {
+ setSecondaryGroupingSize(fieldGetter.get("groupingSize2", (byte) 0));
+ } else if (name.equals("maxSignificantDigits")) {
+ setMaximumSignificantDigits(fieldGetter.get("maxSignificantDigits", 6));
+ } else if (name.equals("minExponentDigits")) {
+ setMinimumExponentDigits(fieldGetter.get("minExponentDigits", (byte) 0));
+ } else if (name.equals("minSignificantDigits")) {
+ setMinimumSignificantDigits(fieldGetter.get("minSignificantDigits", 1));
+ } else if (name.equals("multiplier")) {
+ setMultiplier(fieldGetter.get("multiplier", 1));
+ } else if (name.equals("pad")) {
+ setPadCharacter(fieldGetter.get("pad", '\u0020'));
+ } else if (name.equals("padPosition")) {
+ setPadPosition(fieldGetter.get("padPosition", 0));
+ } else if (name.equals("parseBigDecimal")) {
+ setParseBigDecimal(fieldGetter.get("parseBigDecimal", false));
+ } else if (name.equals("parseRequireDecimalPoint")) {
+ setDecimalPatternMatchRequired(fieldGetter.get("parseRequireDecimalPoint", false));
+ } else if (name.equals("roundingMode")) {
+ setRoundingMode(fieldGetter.get("roundingMode", 0));
+ } else if (name.equals("useExponentialNotation")) {
+ setScientificNotation(fieldGetter.get("useExponentialNotation", false));
+ } else if (name.equals("useSignificantDigits")) {
+ setSignificantDigitsUsed(fieldGetter.get("useSignificantDigits", false));
+ } else if (name.equals("currencyPluralInfo")) {
+ setCurrencyPluralInfo((CurrencyPluralInfo) fieldGetter.get("currencyPluralInfo", null));
+ } else if (name.equals("currencyUsage")) {
+ setCurrencyUsage((CurrencyUsage) fieldGetter.get("currencyUsage", null));
+ } else if (name.equals("mathContext")) {
+ setMathContextICU((MathContext) fieldGetter.get("mathContext", null));
+ } else if (name.equals("negPrefixPattern")) {
+ npp = (String) fieldGetter.get("negPrefixPattern", null);
+ } else if (name.equals("negSuffixPattern")) {
+ nsp = (String) fieldGetter.get("negSuffixPattern", null);
+ } else if (name.equals("negativePrefix")) {
+ np = (String) fieldGetter.get("negativePrefix", null);
+ } else if (name.equals("negativeSuffix")) {
+ ns = (String) fieldGetter.get("negativeSuffix", null);
+ } else if (name.equals("posPrefixPattern")) {
+ ppp = (String) fieldGetter.get("posPrefixPattern", null);
+ } else if (name.equals("posSuffixPattern")) {
+ psp = (String) fieldGetter.get("posSuffixPattern", null);
+ } else if (name.equals("positivePrefix")) {
+ pp = (String) fieldGetter.get("positivePrefix", null);
+ } else if (name.equals("positiveSuffix")) {
+ ps = (String) fieldGetter.get("positiveSuffix", null);
+ } else if (name.equals("roundingIncrement")) {
+ setRoundingIncrement((java.math.BigDecimal) fieldGetter.get("roundingIncrement", null));
+ } else if (name.equals("symbols")) {
+ setDecimalFormatSymbols((DecimalFormatSymbols) fieldGetter.get("symbols", null));
} else {
- return isIntegral ? 0 : getMaximumFractionDigits();
+ // The following fields are ignored:
+ // "PARSE_MAX_EXPONENT"
+ // "currencySignCount"
+ // "style"
+ // "attributes"
+ // "currencyChoice"
+ // "formatPattern"
}
+ }
+ // Resolve affixes
+ if (npp == null) {
+ properties.setNegativePrefix(np);
+ } else {
+ properties.setNegativePrefixPattern(npp);
+ }
+ if (nsp == null) {
+ properties.setNegativeSuffix(ns);
+ } else {
+ properties.setNegativeSuffixPattern(nsp);
+ }
+ if (ppp == null) {
+ properties.setPositivePrefix(pp);
+ } else {
+ properties.setPositivePrefixPattern(ppp);
+ }
+ if (psp == null) {
+ properties.setPositiveSuffix(ps);
+ } else {
+ properties.setPositiveSuffixPattern(psp);
+ }
+ // Extract values from parent NumberFormat class. Have to use reflection here.
+ java.lang.reflect.Field getter;
+ try {
+ getter = NumberFormat.class.getDeclaredField("groupingUsed");
+ getter.setAccessible(true);
+ setGroupingUsed((Boolean) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("parseIntegerOnly");
+ getter.setAccessible(true);
+ setParseIntegerOnly((Boolean) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("maximumIntegerDigits");
+ getter.setAccessible(true);
+ setMaximumIntegerDigits((Integer) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("minimumIntegerDigits");
+ getter.setAccessible(true);
+ setMinimumIntegerDigits((Integer) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("maximumFractionDigits");
+ getter.setAccessible(true);
+ setMaximumFractionDigits((Integer) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("minimumFractionDigits");
+ getter.setAccessible(true);
+ setMinimumFractionDigits((Integer) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("currency");
+ getter.setAccessible(true);
+ setCurrency((Currency) getter.get(this));
+ getter = NumberFormat.class.getDeclaredField("parseStrict");
+ getter.setAccessible(true);
+ setParseStrict((Boolean) getter.get(this));
+ } catch (IllegalArgumentException e) {
+ throw new IOException(e);
+ } catch (IllegalAccessException e) {
+ throw new IOException(e);
+ } catch (NoSuchFieldException e) {
+ throw new IOException(e);
+ } catch (SecurityException e) {
+ throw new IOException(e);
+ }
+ // Finish initialization
+ if (symbols == null) {
+ symbols = getDefaultSymbols();
+ }
+ exportedProperties = new Properties();
+ refreshFormatter();
}
+ }
- private StringBuffer subformat(int number, StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative, boolean isInteger, boolean parseAttr) {
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- // compute the plural category from the digitList plus other settings
- return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
- result, fieldPosition, isNegative,
- isInteger, parseAttr);
- } else {
- return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
- }
- }
+ //=====================================================================================//
+ // FORMAT AND PARSE APIS //
+ //=====================================================================================//
- /**
- * This is ugly, but don't see a better way to do it without major restructuring of the code.
- */
- /*package*/ FixedDecimal getFixedDecimal(double number) {
- // get the visible fractions and the number of fraction digits.
- return getFixedDecimal(number, digitList);
- }
+ /** @stable ICU 2.0 */
+ @Override
+ public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
+ FormatQuantity4 fq = new FormatQuantity4(number);
+ formatter.format(fq, result, fieldPosition);
+ fq.populateUFieldPosition(fieldPosition);
+ return result;
+ }
- FixedDecimal getFixedDecimal(double number, DigitList dl) {
- int fractionalDigitsInDigitList = dl.count - dl.decimalAt;
- int v;
- long f;
- int maxFractionalDigits;
- int minFractionalDigits;
- if (useSignificantDigits) {
- maxFractionalDigits = maxSignificantDigits - dl.decimalAt;
- minFractionalDigits = minSignificantDigits - dl.decimalAt;
- if (minFractionalDigits < 0) {
- minFractionalDigits = 0;
- }
- if (maxFractionalDigits < 0) {
- maxFractionalDigits = 0;
- }
- } else {
- maxFractionalDigits = getMaximumFractionDigits();
- minFractionalDigits = getMinimumFractionDigits();
- }
- v = fractionalDigitsInDigitList;
- if (v < minFractionalDigits) {
- v = minFractionalDigits;
- } else if (v > maxFractionalDigits) {
- v = maxFractionalDigits;
- }
- f = 0;
- if (v > 0) {
- for (int i = Math.max(0, dl.decimalAt); i < dl.count; ++i) {
- f *= 10;
- f += (dl.digits[i] - '0');
- }
- for (int i = v; i < fractionalDigitsInDigitList; ++i) {
- f *= 10;
- }
- }
- return new FixedDecimal(number, v, f);
- }
+ /** @stable ICU 2.0 */
+ @Override
+ public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
+ FormatQuantity4 fq = new FormatQuantity4(number);
+ formatter.format(fq, result, fieldPosition);
+ fq.populateUFieldPosition(fieldPosition);
+ return result;
+ }
- private StringBuffer subformat(double number, StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative,
- boolean isInteger, boolean parseAttr) {
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- // compute the plural category from the digitList plus other settings
- return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
- result, fieldPosition, isNegative,
- isInteger, parseAttr);
- } else {
- return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
- }
- }
+ /** @stable ICU 2.0 */
+ @Override
+ public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
+ FormatQuantity4 fq = new FormatQuantity4(number);
+ formatter.format(fq, result, fieldPosition);
+ fq.populateUFieldPosition(fieldPosition);
+ return result;
+ }
- private StringBuffer subformat(String pluralCount, StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative, boolean isInteger, boolean parseAttr) {
- // There are 2 ways to activate currency plural format: by applying a pattern with
- // 3 currency sign directly, or by instantiate a decimal formatter using
- // PLURALCURRENCYSTYLE. For both cases, the number of currency sign in the
- // pattern is 3. Even if the number of currency sign in the pattern is 3, it does
- // not mean we need to reset the pattern. For 1st case, we do not need to reset
- // pattern. For 2nd case, we might need to reset pattern, if the default pattern
- // (corresponding to plural count 'other') we use is different from the pattern
- // based on 'pluralCount'.
- //
- // style is only valid when decimal formatter is constructed through
- // DecimalFormat(pattern, symbol, style)
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- // May need to reset pattern if the style is PLURALCURRENCYSTYLE.
- String currencyPluralPattern = currencyPluralInfo.getCurrencyPluralPattern(pluralCount);
- if (formatPattern.equals(currencyPluralPattern) == false) {
- applyPatternWithoutExpandAffix(currencyPluralPattern, false);
- }
- }
- // Expand the affix to the right name according to the plural rule. This is only
- // used for currency plural formatting. Currency plural name is not a fixed
- // static one, it is a dynamic name based on the currency plural count. So, the
- // affixes need to be expanded here. For other cases, the affix is a static one
- // based on pattern alone, and it is already expanded during applying pattern, or
- // setDecimalFormatSymbols, or setCurrency.
- expandAffixAdjustWidth(pluralCount);
- return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
- }
+ /** @stable ICU 2.0 */
+ @Override
+ public StringBuffer format(
+ java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
+ FormatQuantity4 fq = new FormatQuantity4(number);
+ formatter.format(fq, result, fieldPosition);
+ fq.populateUFieldPosition(fieldPosition);
+ return result;
+ }
- /**
- * Complete the formatting of a finite number. On entry, the
- * digitList must be filled in with the correct digits.
- */
- private StringBuffer subformat(StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative, boolean isInteger, boolean parseAttr) {
- // NOTE: This isn't required anymore because DigitList takes care of this.
- //
- // // The negative of the exponent represents the number of leading // zeros
- // between the decimal and the first non-zero digit, for // a value < 0.1 (e.g.,
- // for 0.00123, -fExponent == 2). If this // is more than the maximum fraction
- // digits, then we have an underflow // for the printed representation. We
- // recognize this here and set // the DigitList representation to zero in this
- // situation.
- //
- // if (-digitList.decimalAt >= getMaximumFractionDigits())
- // {
- // digitList.count = 0;
- // }
+ /** @stable ICU 2.0 */
+ @Override
+ public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
+ FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
+ formatter.format(fq, result, fieldPosition);
+ fq.populateUFieldPosition(fieldPosition);
+ return result;
+ }
+ /** @stable ICU 3.6 */
+ @Override
+ public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+ if (!(obj instanceof Number)) throw new IllegalArgumentException();
+ Number number = (Number) obj;
+ FormatQuantity4 fq = new FormatQuantity4(number);
+ AttributedCharacterIterator result = formatter.formatToCharacterIterator(fq);
+ return result;
+ }
-
- // Per bug 4147706, DecimalFormat must respect the sign of numbers which format as
- // zero. This allows sensible computations and preserves relations such as
- // signum(1/x) = signum(x), where x is +Infinity or -Infinity. Prior to this fix,
- // we always formatted zero values as if they were positive. Liu 7/6/98.
- if (digitList.isZero()) {
- digitList.decimalAt = 0; // Normalize
+ protected static final ThreadLocal If the affix was specified via the pattern, the string returned by this method will have
+ * locale symbols substituted in place of special characters according to the LDML specification.
+ * If the affix was specified via {@link #setPositivePrefix}, the string will be returned
+ * literally.
+ *
+ * @return The string being prepended to positive numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized String getPositivePrefix() {
+ String result = exportedProperties.getPositivePrefix();
+ return (result == null) ? "" : result;
+ }
- // Handle NaN as a special case:
+ /**
+ * Affixes: Sets the string to prepend to positive numbers. For example, if you
+ * set the value "#", then the number 123 will be formatted as "#123" in the locale
+ * en-US.
+ *
+ * Using this method overrides the affix specified via the pattern, and unlike the pattern, the
+ * string given to this method will be interpreted literally WITHOUT locale symbol substitutions.
+ *
+ * @param prefix The literal string to prepend to positive numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized void setPositivePrefix(String prefix) {
+ properties.setPositivePrefix(prefix);
+ refreshFormatter();
+ }
- // Skip padding characters, if around prefix
- if (formatWidth > 0 &&
- (padPosition == PAD_BEFORE_PREFIX || padPosition == PAD_AFTER_PREFIX)) {
- i = skipPadding(text, i);
- }
- if (text.regionMatches(i, symbols.getNaN(), 0, symbols.getNaN().length())) {
- i += symbols.getNaN().length();
- // Skip padding characters, if around suffix
- if (formatWidth > 0 && (padPosition == PAD_BEFORE_SUFFIX ||
- padPosition == PAD_AFTER_SUFFIX)) {
- i = skipPadding(text, i);
- }
- parsePosition.setIndex(i);
- return new Double(Double.NaN);
- }
+ /**
+ * Affixes: Gets the negative prefix string currently being used to format
+ * numbers.
+ *
+ * If the affix was specified via the pattern, the string returned by this method will have
+ * locale symbols substituted in place of special characters according to the LDML specification.
+ * If the affix was specified via {@link #setNegativePrefix}, the string will be returned
+ * literally.
+ *
+ * @return The string being prepended to negative numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized String getNegativePrefix() {
+ String result = exportedProperties.getNegativePrefix();
+ return (result == null) ? "" : result;
+ }
- // NaN parse failed; start over
- i = backup;
+ /**
+ * Affixes: Sets the string to prepend to negative numbers. For example, if you
+ * set the value "#", then the number -123 will be formatted as "#123" in the locale
+ * en-US (overriding the implicit default '-' in the pattern).
+ *
+ * Using this method overrides the affix specified via the pattern, and unlike the pattern, the
+ * string given to this method will be interpreted literally WITHOUT locale symbol substitutions.
+ *
+ * @param suffix The literal string to prepend to negative numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized void setNegativePrefix(String suffix) {
+ properties.setNegativePrefix(suffix);
+ refreshFormatter();
+ }
- boolean[] status = new boolean[STATUS_LENGTH];
- if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
- if (!parseForCurrency(text, parsePosition, currency, status)) {
- return null;
- }
- } else if (currency != null) {
- return null;
- } else {
- if (!subparse(text, parsePosition, digitList, status, currency, negPrefixPattern,
- negSuffixPattern, posPrefixPattern, posSuffixPattern,
- false, Currency.SYMBOL_NAME)) {
- parsePosition.setIndex(backup);
- return null;
- }
- }
+ /**
+ * Affixes: Gets the positive suffix string currently being used to format
+ * numbers.
+ *
+ * If the affix was specified via the pattern, the string returned by this method will have
+ * locale symbols substituted in place of special characters according to the LDML specification.
+ * If the affix was specified via {@link #setPositiveSuffix}, the string will be returned
+ * literally.
+ *
+ * @return The string being appended to positive numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized String getPositiveSuffix() {
+ String result = exportedProperties.getPositiveSuffix();
+ return (result == null) ? "" : result;
+ }
- Number n = null;
+ /**
+ * Affixes: Sets the string to append to positive numbers. For example, if you
+ * set the value "#", then the number 123 will be formatted as "123#" in the locale
+ * en-US.
+ *
+ * Using this method overrides the affix specified via the pattern, and unlike the pattern, the
+ * string given to this method will be interpreted literally WITHOUT locale symbol substitutions.
+ *
+ * @param suffix The literal string to append to positive numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized void setPositiveSuffix(String suffix) {
+ properties.setPositiveSuffix(suffix);
+ refreshFormatter();
+ }
- // Handle infinity
- if (status[STATUS_INFINITE]) {
- n = new Double(status[STATUS_POSITIVE] ? Double.POSITIVE_INFINITY :
- Double.NEGATIVE_INFINITY);
- }
+ /**
+ * Affixes: Gets the negative suffix string currently being used to format
+ * numbers.
+ *
+ * If the affix was specified via the pattern, the string returned by this method will have
+ * locale symbols substituted in place of special characters according to the LDML specification.
+ * If the affix was specified via {@link #setNegativeSuffix}, the string will be returned
+ * literally.
+ *
+ * @return The string being appended to negative numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized String getNegativeSuffix() {
+ String result = exportedProperties.getNegativeSuffix();
+ return (result == null) ? "" : result;
+ }
- // Handle underflow
- else if (status[STATUS_UNDERFLOW]) {
- n = status[STATUS_POSITIVE] ? new Double("0.0") : new Double("-0.0");
- }
+ /**
+ * Affixes: Sets the string to append to negative numbers. For example, if you
+ * set the value "#", then the number 123 will be formatted as "123#" in the locale
+ * en-US.
+ *
+ * Using this method overrides the affix specified via the pattern, and unlike the pattern, the
+ * string given to this method will be interpreted literally WITHOUT locale symbol substitutions.
+ *
+ * @param suffix The literal string to append to negative numbers.
+ * @category Affixes
+ * @stable ICU 2.0
+ */
+ public synchronized void setNegativeSuffix(String suffix) {
+ properties.setNegativeSuffix(suffix);
+ refreshFormatter();
+ }
- // Handle -0.0
- else if (!status[STATUS_POSITIVE] && digitList.isZero()) {
- n = new Double("-0.0");
- }
-
- else {
- // Do as much of the multiplier conversion as possible without
- // losing accuracy.
- int mult = multiplier; // Don't modify this.multiplier
- while (mult % 10 == 0) {
- --digitList.decimalAt;
- mult /= 10;
- }
-
- // Handle integral values
- if (!parseBigDecimal && mult == 1 && digitList.isIntegral()) {
- // hack quick long
- if (digitList.decimalAt < 12) { // quick check for long
- long l = 0;
- if (digitList.count > 0) {
- int nx = 0;
- while (nx < digitList.count) {
- l = l * 10 + (char) digitList.digits[nx++] - '0';
- }
- while (nx++ < digitList.decimalAt) {
- l *= 10;
- }
- if (!status[STATUS_POSITIVE]) {
- l = -l;
- }
- }
- n = Long.valueOf(l);
- } else {
- BigInteger big = digitList.getBigInteger(status[STATUS_POSITIVE]);
- n = (big.bitLength() < 64) ? (Number) Long.valueOf(big.longValue()) : (Number) big;
- }
- }
- // Handle non-integral values or the case where parseBigDecimal is set
- else {
- BigDecimal big = digitList.getBigDecimalICU(status[STATUS_POSITIVE]);
- n = big;
- if (mult != 1) {
- n = big.divide(BigDecimal.valueOf(mult), mathContext);
- }
- }
- }
-
- // Assemble into CurrencyAmount if necessary
- return (currency != null) ? (Object) new CurrencyAmount(n, currency[0]) : (Object) n;
+ /**
+ * @return The multiplier being applied to numbers before they are formatted.
+ * @see #setMultiplier
+ * @category Multipliers
+ * @stable ICU 2.0
+ */
+ public synchronized int getMultiplier() {
+ if (properties.getMultiplier() != null) {
+ return properties.getMultiplier().intValue();
+ } else {
+ return (int) Math.pow(10, properties.getMagnitudeMultiplier());
}
+ }
- private boolean parseForCurrency(String text, ParsePosition parsePosition,
- Currency[] currency, boolean[] status) {
- int origPos = parsePosition.getIndex();
- if (!isReadyForParsing) {
- int savedCurrencySignCount = currencySignCount;
- setupCurrencyAffixForAllPatterns();
- // reset pattern back
- if (savedCurrencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- applyPatternWithoutExpandAffix(formatPattern, false);
- } else {
- applyPattern(formatPattern, false);
- }
- isReadyForParsing = true;
- }
- int maxPosIndex = origPos;
- int maxErrorPos = -1;
- boolean[] savedStatus = null;
- // First, parse against current pattern.
- // Since current pattern could be set by applyPattern(),
- // it could be an arbitrary pattern, and it may not be the one
- // defined in current locale.
- boolean[] tmpStatus = new boolean[STATUS_LENGTH];
- ParsePosition tmpPos = new ParsePosition(origPos);
- DigitList tmpDigitList = new DigitList();
- boolean found;
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- true, Currency.LONG_NAME);
- } else {
- found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- true, Currency.SYMBOL_NAME);
- }
- if (found) {
- if (tmpPos.getIndex() > maxPosIndex) {
- maxPosIndex = tmpPos.getIndex();
- savedStatus = tmpStatus;
- digitList = tmpDigitList;
- }
- } else {
- maxErrorPos = tmpPos.getErrorIndex();
- }
- // Then, parse against affix patterns. Those are currency patterns and currency
- // plural patterns defined in the locale.
- for (AffixForCurrency affix : affixPatternsForCurrency) {
- tmpStatus = new boolean[STATUS_LENGTH];
- tmpPos = new ParsePosition(origPos);
- tmpDigitList = new DigitList();
- boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- affix.getNegPrefix(), affix.getNegSuffix(),
- affix.getPosPrefix(), affix.getPosSuffix(),
- true, affix.getPatternType());
- if (result) {
- found = true;
- if (tmpPos.getIndex() > maxPosIndex) {
- maxPosIndex = tmpPos.getIndex();
- savedStatus = tmpStatus;
- digitList = tmpDigitList;
- }
- } else {
- maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? tmpPos.getErrorIndex()
- : maxErrorPos;
- }
- }
- // Finally, parse against simple affix to find the match. For example, in
- // TestMonster suite, if the to-be-parsed text is "-\u00A40,00".
- // complexAffixCompare will not find match, since there is no ISO code matches
- // "\u00A4", and the parse stops at "\u00A4". We will just use simple affix
- // comparison (look for exact match) to pass it.
- //
- // TODO: We should parse against simple affix first when
- // output currency is not requested. After the complex currency
- // parsing implementation was introduced, the default currency
- // instance parsing slowed down because of the new code flow.
- // I filed #10312 - Yoshito
- tmpStatus = new boolean[STATUS_LENGTH];
- tmpPos = new ParsePosition(origPos);
- tmpDigitList = new DigitList();
-
- // Disable complex currency parsing and try it again.
- boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- negativePrefix, negativeSuffix, positivePrefix, positiveSuffix,
- false /* disable complex currency parsing */, Currency.SYMBOL_NAME);
- if (result) {
- if (tmpPos.getIndex() > maxPosIndex) {
- maxPosIndex = tmpPos.getIndex();
- savedStatus = tmpStatus;
- digitList = tmpDigitList;
- }
- found = true;
- } else {
- maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? tmpPos.getErrorIndex() :
- maxErrorPos;
- }
-
- if (!found) {
- // parsePosition.setIndex(origPos);
- parsePosition.setErrorIndex(maxErrorPos);
- } else {
- parsePosition.setIndex(maxPosIndex);
- parsePosition.setErrorIndex(-1);
- for (int index = 0; index < STATUS_LENGTH; ++index) {
- status[index] = savedStatus[index];
- }
- }
- return found;
+ /**
+ * Sets a number that will be used to multiply all numbers prior to formatting. For example, when
+ * formatting percents, a multiplier of 100 can be used.
+ *
+ * If a percent or permille sign is specified in the pattern, the multiplier is automatically
+ * set to 100 or 1000, respectively.
+ *
+ * If the number specified here is a power of 10, a more efficient code path will be used.
+ *
+ * @param multiplier The number by which all numbers passed to {@link #format} will be multiplied.
+ * @throws IllegalArgumentException If the given multiplier is zero.
+ * @category Multipliers
+ * @stable ICU 2.0
+ */
+ public synchronized void setMultiplier(int multiplier) {
+ if (multiplier == 0) {
+ throw new IllegalArgumentException("Multiplier must be nonzero.");
}
- // Get affix patterns used in locale's currency pattern (NumberPatterns[1]) and
- // currency plural pattern (CurrencyUnitPatterns).
- private void setupCurrencyAffixForAllPatterns() {
- if (currencyPluralInfo == null) {
- currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
- }
- affixPatternsForCurrency = new HashSet The rounding increment can be specified via the pattern string: for example, the pattern
+ * "#,##0.05" encodes a rounding increment of 0.05.
+ *
+ * The rounding increment is applied after any multipliers might take effect; for
+ * example, in scientific notation or when {@link #setMultiplier} is used.
+ *
+ * See {@link #setMaximumFractionDigits} and {@link #setMaximumSignificantDigits} for two other
+ * ways of specifying rounding strategies.
+ *
+ * @param increment The increment to which numbers are to be rounded.
+ * @see #setRoundingMode
+ * @see #setMaximumFractionDigits
+ * @see #setMaximumSignificantDigits
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ public synchronized void setRoundingIncrement(java.math.BigDecimal increment) {
+ // Backwards compatibility: ignore rounding increment if zero,
+ // and instead set maximum fraction digits.
+ if (increment != null && increment.compareTo(java.math.BigDecimal.ZERO) == 0) {
+ properties.setMaximumFractionDigits(Integer.MAX_VALUE);
+ return;
}
- /**
- * Returns a set of characters equivalent to the given desimal separator used for
- * parsing number. This method may return an empty set.
- */
- private UnicodeSet getEquivalentDecimals(String decimal, boolean strictParse) {
- UnicodeSet equivSet = UnicodeSet.EMPTY;
- if (strictParse) {
- if (strictDotEquivalents.contains(decimal)) {
- equivSet = strictDotEquivalents;
- } else if (strictCommaEquivalents.contains(decimal)) {
- equivSet = strictCommaEquivalents;
- }
- } else {
- if (dotEquivalents.contains(decimal)) {
- equivSet = dotEquivalents;
- } else if (commaEquivalents.contains(decimal)) {
- equivSet = commaEquivalents;
- }
- }
- return equivSet;
- }
+ properties.setRoundingIncrement(increment);
+ refreshFormatter();
+ }
- /**
- * Starting at position, advance past a run of pad characters, if any. Return the
- * index of the first character after position that is not a pad character. Result is
- * >= position.
- */
- private final int skipPadding(String text, int position) {
- while (position < text.length() && text.charAt(position) == pad) {
- ++position;
- }
- return position;
- }
+ /**
+ * Rounding and Digit Limits: Overload of {@link
+ * #setRoundingIncrement(java.math.BigDecimal)}.
+ *
+ * @param increment The increment to which numbers are to be rounded.
+ * @see #setRoundingIncrement
+ * @category Rounding
+ * @stable ICU 3.6
+ */
+ public synchronized void setRoundingIncrement(BigDecimal increment) {
+ java.math.BigDecimal javaBigDecimal = (increment == null) ? null : increment.toBigDecimal();
+ setRoundingIncrement(javaBigDecimal);
+ }
- /**
- * Returns the length matched by the given affix, or -1 if none. Runs of white space
- * in the affix, match runs of white space in the input. Pattern white space and input
- * white space are determined differently; see code.
- *
- * @param text input text
- * @param pos offset into input at which to begin matching
- * @param isNegative
- * @param isPrefix
- * @param affixPat affix pattern used for currency affix comparison
- * @param complexCurrencyParsing whether it is currency parsing or not
- * @param type compare against currency type, LONG_NAME only or not.
- * @param currency return value for parsed currency, for generic currency parsing
- * mode, or null for normal parsing. In generic currency parsing mode, any currency
- * is parsed, not just the currency that this formatter is set to.
- * @return length of input that matches, or -1 if match failure
- */
- private int compareAffix(String text, int pos, boolean isNegative, boolean isPrefix,
- String affixPat, boolean complexCurrencyParsing, int type, Currency[] currency) {
- if (currency != null || currencyChoice != null || (currencySignCount != CURRENCY_SIGN_COUNT_ZERO && complexCurrencyParsing)) {
- return compareComplexAffix(affixPat, text, pos, type, currency);
- }
- if (isPrefix) {
- return compareSimpleAffix(isNegative ? negativePrefix : positivePrefix, text, pos);
- } else {
- return compareSimpleAffix(isNegative ? negativeSuffix : positiveSuffix, text, pos);
- }
-
+ /**
+ * Rounding and Digit Limits: Overload of {@link
+ * #setRoundingIncrement(java.math.BigDecimal)}.
+ *
+ * @param increment The increment to which numbers are to be rounded.
+ * @see #setRoundingIncrement
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ public synchronized void setRoundingIncrement(double increment) {
+ if (increment == 0) {
+ setRoundingIncrement((java.math.BigDecimal) null);
+ } else {
+ java.math.BigDecimal javaBigDecimal = java.math.BigDecimal.valueOf(increment);
+ setRoundingIncrement(javaBigDecimal);
}
+ }
- /**
- * Check for bidi marks: LRM, RLM, ALM
- */
- private static boolean isBidiMark(int c) {
- return (c==0x200E || c==0x200F || c==0x061C);
- }
+ /**
+ * @return The rounding mode being used to round numbers.
+ * @see #setRoundingMode
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized int getRoundingMode() {
+ RoundingMode mode = exportedProperties.getRoundingMode();
+ return (mode == null) ? 0 : mode.ordinal();
+ }
- /**
- * Remove bidi marks from affix
- */
- private static String trimMarksFromAffix(String affix) {
- boolean hasBidiMark = false;
- int idx = 0;
- for (; idx < affix.length(); idx++) {
- if (isBidiMark(affix.charAt(idx))) {
- hasBidiMark = true;
- break;
- }
- }
- if (!hasBidiMark) {
- return affix;
- }
+ /**
+ * Rounding and Digit Limits: Sets the {@link RoundingMode} used to round
+ * numbers. The default rounding mode is HALF_EVEN, which rounds decimals to their closest whole
+ * number, and rounds to the closest even number if at the midpoint.
+ *
+ * For more detail on rounding modes, see the ICU User
+ * Guide.
+ *
+ * For backwards compatibility, the rounding mode is specified as an int argument, which can be
+ * from either the constants in {@link BigDecimal} or the ordinal value of {@link RoundingMode}.
+ * The following two calls are functionally equivalent.
+ *
+ * This method is provided for users who require their output to conform to a standard math
+ * context. Most users should call {@link #setRoundingMode} and/or {@link
+ * #setMaximumSignificantDigits} instead of this method.
+ *
+ * @param mathContext The MathContext to use when rounding numbers.
+ * @see java.math.MathContext
+ * @category Rounding
+ * @stable ICU 4.2
+ */
+ public synchronized void setMathContext(java.math.MathContext mathContext) {
+ properties.setMathContext(mathContext);
+ refreshFormatter();
+ }
- /**
- * Return the length matched by the given affix, or -1 if none. Runs of white space in
- * the affix, match runs of white space in the input. Pattern white space and input
- * white space are determined differently; see code.
- *
- * @param affix pattern string, taken as a literal
- * @param input input text
- * @param pos offset into input at which to begin matching
- * @return length of input that matches, or -1 if match failure
- */
- private static int compareSimpleAffix(String affix, String input, int pos) {
- int start = pos;
- // Affixes here might consist of sign, currency symbol and related spacing, etc.
- // For more efficiency we should keep lazily-created trimmed affixes around in
- // instance variables instead of trimming each time they are used (the next step).
- String trimmedAffix = (affix.length() > 1)? trimMarksFromAffix(affix): affix;
- for (int i = 0; i < trimmedAffix.length();) {
- int c = UTF16.charAt(trimmedAffix, i);
- int len = UTF16.getCharCount(c);
- if (PatternProps.isWhiteSpace(c)) {
- // We may have a pattern like: \u200F and input text like: \u200F Note
- // that U+200F and U+0020 are Pattern_White_Space but only U+0020 is
- // UWhiteSpace. So we have to first do a direct match of the run of RULE
- // whitespace in the pattern, then match any extra characters.
- boolean literalMatch = false;
- while (pos < input.length()) {
- int ic = UTF16.charAt(input, pos);
- if (ic == c) {
- literalMatch = true;
- i += len;
- pos += len;
- if (i == trimmedAffix.length()) {
- break;
- }
- c = UTF16.charAt(trimmedAffix, i);
- len = UTF16.getCharCount(c);
- if (!PatternProps.isWhiteSpace(c)) {
- break;
- }
- } else if (isBidiMark(ic)) {
- pos++; // just skip over this input text
- } else {
- break;
- }
- }
+ // Remember the ICU math context form in order to be able to return it from the API.
+ // NOTE: This value is not serialized. (should it be?)
+ private transient int icuMathContextForm = MathContext.PLAIN;
- // Advance over run in trimmedAffix
- i = skipPatternWhiteSpace(trimmedAffix, i);
+ /**
+ * @return The {@link com.ibm.icu.math.MathContext} being used to round numbers.
+ * @see #setMathContext
+ * @category Rounding
+ * @stable ICU 4.2
+ */
+ public synchronized MathContext getMathContextICU() {
+ java.math.MathContext mathContext = getMathContext();
+ return new MathContext(
+ mathContext.getPrecision(),
+ icuMathContextForm,
+ false,
+ mathContext.getRoundingMode().ordinal());
+ }
- // Advance over run in input text. Must see at least one white space char
- // in input, unless we've already matched some characters literally.
- int s = pos;
- pos = skipUWhiteSpace(input, pos);
- if (pos == s && !literalMatch) {
- return -1;
- }
- // If we skip UWhiteSpace in the input text, we need to skip it in the
- // pattern. Otherwise, the previous lines may have skipped over text
- // (such as U+00A0) that is also in the trimmedAffix.
- i = skipUWhiteSpace(trimmedAffix, i);
- } else {
- boolean match = false;
- while (pos < input.length()) {
- int ic = UTF16.charAt(input, pos);
- if (!match && equalWithSignCompatibility(ic, c)) {
- i += len;
- pos += len;
- match = true;
- } else if (isBidiMark(ic)) {
- pos++; // just skip over this input text
- } else {
- break;
- }
- }
- if (!match) {
- return -1;
- }
- }
- }
- return pos - start;
+ /**
+ * Rounding and Digit Limits: Overload of {@link #setMathContext} for {@link
+ * com.ibm.icu.math.MathContext}.
+ *
+ * @param mathContextICU The MathContext to use when rounding numbers.
+ * @see #setMathContext(java.math.MathContext)
+ * @category Rounding
+ * @stable ICU 4.2
+ */
+ public synchronized void setMathContextICU(MathContext mathContextICU) {
+ icuMathContextForm = mathContextICU.getForm();
+ java.math.MathContext mathContext;
+ if (mathContextICU.getLostDigits()) {
+ // The getLostDigits() feature in ICU MathContext means "throw an ArithmeticException if
+ // rounding causes digits to be lost". That feature is called RoundingMode.UNNECESSARY in
+ // Java MathContext.
+ mathContext = new java.math.MathContext(mathContextICU.getDigits(), RoundingMode.UNNECESSARY);
+ } else {
+ mathContext =
+ new java.math.MathContext(
+ mathContextICU.getDigits(), RoundingMode.valueOf(mathContextICU.getRoundingMode()));
}
+ setMathContext(mathContext);
+ }
- private static boolean equalWithSignCompatibility(int lhs, int rhs) {
- return lhs == rhs
- || (minusSigns.contains(lhs) && minusSigns.contains(rhs))
- || (plusSigns.contains(lhs) && plusSigns.contains(rhs));
- }
+ /**
+ * @return The effective minimum number of digits before the decimal separator.
+ * @see #setMinimumIntegerDigits
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized int getMinimumIntegerDigits() {
+ return exportedProperties.getMinimumIntegerDigits();
+ }
- /**
- * Skips over a run of zero or more Pattern_White_Space characters at pos in text.
- */
- private static int skipPatternWhiteSpace(String text, int pos) {
- while (pos < text.length()) {
- int c = UTF16.charAt(text, pos);
- if (!PatternProps.isWhiteSpace(c)) {
- break;
- }
- pos += UTF16.getCharCount(c);
- }
- return pos;
- }
+ /**
+ * Rounding and Digit Limits: Sets the minimum number of digits to display before
+ * the decimal separator. If the number has fewer than this many digits, the number is padded with
+ * zeros.
+ *
+ * For example, if minimum integer digits is 3, the number 12.3 will be printed as "001.23".
+ *
+ * Minimum integer and minimum and maximum fraction digits can be specified via the pattern
+ * string. For example, "#,#00.00#" has 2 minimum integer digits, 2 minimum fraction digits, and 3
+ * maximum fraction digits. Note that it is not possible to specify maximium integer digits in the
+ * pattern except in scientific notation.
+ *
+ * @param value The minimum number of digits before the decimal separator.
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setMinimumIntegerDigits(int value) {
+ properties.setMinimumIntegerDigits(value);
+ refreshFormatter();
+ }
- /**
- * Skips over a run of zero or more isUWhiteSpace() characters at pos in text.
- */
- private static int skipUWhiteSpace(String text, int pos) {
- while (pos < text.length()) {
- int c = UTF16.charAt(text, pos);
- if (!UCharacter.isUWhiteSpace(c)) {
- break;
- }
- pos += UTF16.getCharCount(c);
- }
- return pos;
- }
+ /**
+ * @return The effective maximum number of digits before the decimal separator.
+ * @see #setMaximumIntegerDigits
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized int getMaximumIntegerDigits() {
+ return exportedProperties.getMaximumIntegerDigits();
+ }
- /**
- * Skips over a run of zero or more bidi marks at pos in text.
- */
- private static int skipBidiMarks(String text, int pos) {
- while (pos < text.length()) {
- int c = UTF16.charAt(text, pos);
- if (!isBidiMark(c)) {
- break;
- }
- pos += UTF16.getCharCount(c);
- }
- return pos;
- }
+ /**
+ * Rounding and Digit Limits: Sets the maximum number of digits to display before
+ * the decimal separator. If the number has more than this many digits, the number is truncated.
+ *
+ * For example, if maximum integer digits is 3, the number 12345 will be printed as "345".
+ *
+ * Minimum integer and minimum and maximum fraction digits can be specified via the pattern
+ * string. For example, "#,#00.00#" has 2 minimum integer digits, 2 minimum fraction digits, and 3
+ * maximum fraction digits. Note that it is not possible to specify maximium integer digits in the
+ * pattern except in scientific notation.
+ *
+ * @param value The maximum number of digits before the decimal separator.
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setMaximumIntegerDigits(int value) {
+ properties.setMaximumIntegerDigits(value);
+ refreshFormatter();
+ }
- /**
- * Returns the length matched by the given affix, or -1 if none.
- *
- * @param affixPat pattern string
- * @param text input text
- * @param pos offset into input at which to begin matching
- * @param type parse against currency type, LONG_NAME only or not.
- * @param currency return value for parsed currency, for generic
- * currency parsing mode, or null for normal parsing. In generic
- * currency parsing mode, any currency is parsed, not just the
- * currency that this formatter is set to.
- * @return position after the matched text, or -1 if match failure
- */
- private int compareComplexAffix(String affixPat, String text, int pos, int type,
- Currency[] currency) {
- int start = pos;
- for (int i = 0; i < affixPat.length() && pos >= 0;) {
- char c = affixPat.charAt(i++);
- if (c == QUOTE) {
- for (;;) {
- int j = affixPat.indexOf(QUOTE, i);
- if (j == i) {
- pos = match(text, pos, QUOTE);
- i = j + 1;
- break;
- } else if (j > i) {
- pos = match(text, pos, affixPat.substring(i, j));
- i = j + 1;
- if (i < affixPat.length() && affixPat.charAt(i) == QUOTE) {
- pos = match(text, pos, QUOTE);
- ++i;
- // loop again
- } else {
- break;
- }
- } else {
- // Unterminated quote; should be caught by apply
- // pattern.
- throw new RuntimeException();
- }
- }
- continue;
- }
+ /**
+ * @return The effective minimum number of integer digits after the decimal separator.
+ * @see #setMaximumIntegerDigits
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized int getMinimumFractionDigits() {
+ return exportedProperties.getMinimumFractionDigits();
+ }
- String affix = null;
+ /**
+ * Rounding and Digit Limits: Sets the minimum number of digits to display after
+ * the decimal separator. If the number has fewer than this many digits, the number is padded with
+ * zeros.
+ *
+ * For example, if minimum fraction digits is 2, the number 123.4 will be printed as "123.40".
+ *
+ * Minimum integer and minimum and maximum fraction digits can be specified via the pattern
+ * string. For example, "#,#00.00#" has 2 minimum integer digits, 2 minimum fraction digits, and 3
+ * maximum fraction digits. Note that it is not possible to specify maximium integer digits in the
+ * pattern except in scientific notation.
+ *
+ * See {@link #setRoundingIncrement} and {@link #setMaximumSignificantDigits} for two other
+ * ways of specifying rounding strategies.
+ *
+ * @param value The minimum number of integer digits after the decimal separator.
+ * @see #setRoundingMode
+ * @see #setRoundingIncrement
+ * @see #setMaximumSignificantDigits
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setMinimumFractionDigits(int value) {
+ properties.setMinimumFractionDigits(value);
+ refreshFormatter();
+ }
- switch (c) {
- case CURRENCY_SIGN:
- // since the currency names in choice format is saved the same way as
- // other currency names, do not need to do currency choice parsing here.
- // the general currency parsing parse against all names, including names
- // in choice format. assert(currency != null || (getCurrency() != null &&
- // currencyChoice != null));
- boolean intl = i < affixPat.length() && affixPat.charAt(i) == CURRENCY_SIGN;
- if (intl) {
- ++i;
- }
- boolean plural = i < affixPat.length() && affixPat.charAt(i) == CURRENCY_SIGN;
- if (plural) {
- ++i;
- intl = false;
- }
- // Parse generic currency -- anything for which we have a display name, or
- // any 3-letter ISO code. Try to parse display name for our locale; first
- // determine our locale. TODO: use locale in CurrencyPluralInfo
- ULocale uloc = getLocale(ULocale.VALID_LOCALE);
- if (uloc == null) {
- // applyPattern has been called; use the symbols
- uloc = symbols.getLocale(ULocale.VALID_LOCALE);
- }
- // Delegate parse of display name => ISO code to Currency
- ParsePosition ppos = new ParsePosition(pos);
- // using Currency.parse to handle mixed style parsing.
- String iso = Currency.parse(uloc, text, type, ppos);
+ /**
+ * @return The effective maximum number of integer digits after the decimal separator.
+ * @see #setMaximumIntegerDigits
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized int getMaximumFractionDigits() {
+ return exportedProperties.getMaximumFractionDigits();
+ }
- // If parse succeeds, populate currency[0]
- if (iso != null) {
- if (currency != null) {
- currency[0] = Currency.getInstance(iso);
- } else {
- // The formatter is currency-style but the client has not requested
- // the value of the parsed currency. In this case, if that value does
- // not match the formatter's current value, then the parse fails.
- Currency effectiveCurr = getEffectiveCurrency();
- if (iso.compareTo(effectiveCurr.getCurrencyCode()) != 0) {
- pos = -1;
- continue;
- }
- }
- pos = ppos.getIndex();
- } else {
- pos = -1;
- }
- continue;
- case PATTERN_PERCENT:
- affix = symbols.getPercentString();
- break;
- case PATTERN_PER_MILLE:
- affix = symbols.getPerMillString();
- break;
- case PATTERN_PLUS_SIGN:
- affix = symbols.getPlusSignString();
- break;
- case PATTERN_MINUS_SIGN:
- affix = symbols.getMinusSignString();
- break;
- default:
- // fall through to affix != null test, which will fail
- break;
- }
+ /**
+ * Rounding and Digit Limits: Sets the maximum number of digits to display after
+ * the decimal separator. If the number has more than this many digits, the number is rounded
+ * according to the rounding mode.
+ *
+ * For example, if maximum fraction digits is 2, the number 123.456 will be printed as
+ * "123.46".
+ *
+ * Minimum integer and minimum and maximum fraction digits can be specified via the pattern
+ * string. For example, "#,#00.00#" has 2 minimum integer digits, 2 minimum fraction digits, and 3
+ * maximum fraction digits. Note that it is not possible to specify maximium integer digits in the
+ * pattern except in scientific notation.
+ *
+ * @param value The maximum number of integer digits after the decimal separator.
+ * @see #setRoundingMode
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setMaximumFractionDigits(int value) {
+ properties.setMaximumFractionDigits(value);
+ refreshFormatter();
+ }
- if (affix != null) {
- pos = match(text, pos, affix);
- continue;
- }
+ /**
+ * @return Whether significant digits are being used in rounding.
+ * @see #setSignificantDigitsUsed
+ * @category Rounding
+ * @stable ICU 3.0
+ */
+ public synchronized boolean areSignificantDigitsUsed() {
+ return SignificantDigitsRounder.useSignificantDigits(properties);
+ }
- pos = match(text, pos, c);
- if (PatternProps.isWhiteSpace(c)) {
- i = skipPatternWhiteSpace(affixPat, i);
- }
- }
-
- return pos - start;
+ /**
+ * Rounding and Digit Limits: Sets whether significant digits are to be used in
+ * rounding.
+ *
+ * Calling For example, if minimum significant digits is 3 and the number is 1.2, the number will be
+ * printed as "1.20".
+ *
+ * @param value The minimum number of significant digits to display.
+ * @see #setSignificantDigitsMode
+ * @category Rounding
+ * @stable ICU 3.0
+ */
+ public synchronized void setMinimumSignificantDigits(int value) {
+ properties.setMinimumSignificantDigits(value);
+ refreshFormatter();
+ }
- /**
- * Returns a copy of the decimal format symbols used by this format.
- *
- * @return desired DecimalFormatSymbols
- * @see DecimalFormatSymbols
- * @stable ICU 2.0
- */
- public DecimalFormatSymbols getDecimalFormatSymbols() {
- try {
- // don't allow multiple references
- return (DecimalFormatSymbols) symbols.clone();
- } catch (Exception foo) {
- return null; // should never happen
- }
- }
+ /**
+ * @return The effective maximum number of significant digits displayed.
+ * @see #setMaximumSignificantDigits
+ * @category Rounding
+ * @stable ICU 3.0
+ */
+ public synchronized int getMaximumSignificantDigits() {
+ return exportedProperties.getMaximumSignificantDigits();
+ }
- /**
- * Sets the decimal format symbols used by this format. The format uses a copy of the
- * provided symbols.
- *
- * @param newSymbols desired DecimalFormatSymbols
- * @see DecimalFormatSymbols
- * @stable ICU 2.0
- */
- public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) {
- symbols = (DecimalFormatSymbols) newSymbols.clone();
- setCurrencyForSymbols();
- expandAffixes(null);
- }
+ /**
+ * Rounding and Digit Limits: Sets the maximum number of significant digits to be
+ * displayed. If the number of significant digits in the number exceeds this value, the number
+ * will be rounded according to the current rounding mode.
+ *
+ * For example, if maximum significant digits is 3 and the number is 12345, the number will be
+ * printed as "12300".
+ *
+ * See {@link #setRoundingIncrement} and {@link #setMaximumFractionDigits} for two other ways
+ * of specifying rounding strategies.
+ *
+ * @param value The maximum number of significant digits to display.
+ * @see #setRoundingMode
+ * @see #setRoundingIncrement
+ * @see #setMaximumFractionDigits
+ * @see #setSignificantDigitsMode
+ * @category Rounding
+ * @stable ICU 3.0
+ */
+ public synchronized void setMaximumSignificantDigits(int value) {
+ properties.setMaximumSignificantDigits(value);
+ refreshFormatter();
+ }
- /**
- * Update the currency object to match the symbols. This method is used only when the
- * caller has passed in a symbols object that may not be the default object for its
- * locale.
- */
- private void setCurrencyForSymbols() {
+ /**
+ * @return The current significant digits mode.
+ * @see #setSignificantDigitsMode
+ * @category Rounding
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized SignificantDigitsMode getSignificantDigitsMode() {
+ return exportedProperties.getSignificantDigitsMode();
+ }
- // Bug 4212072 Update the affix strings according to symbols in order to keep the
- // affix strings up to date. [Richard/GCL]
+ /**
+ * Rounding and Digit Limits: Sets the strategy used for resolving
+ * minimum/maximum significant digits when minimum/maximum integer and/or fraction digits are
+ * specified. There are three modes:
+ *
+ * The following table illustrates the difference. Below, minFrac=1, maxFrac=2, minSig=3, and
+ * maxSig=4:
+ *
+ * If the number is longer than your padding width, the number will display as if no padding
+ * width had been specified, which may result in strings longer than the padding width.
+ *
+ * Padding can be specified in the pattern string using the '*' symbol. For example, the format
+ * "*x######0" has a format width of 7 and a pad character of 'x'.
+ *
+ * Padding is currently counted in UTF-16 code units; see ticket #13034 for more information.
+ *
+ * @param width The minimum number of characters in the output.
+ * @see #setPadCharacter
+ * @see #setPadPosition
+ * @category Padding
+ * @stable ICU 2.0
+ */
+ public synchronized void setFormatWidth(int width) {
+ properties.setFormatWidth(width);
+ refreshFormatter();
+ }
- /**
- * Returns the positive prefix.
- *
- * Examples: +123, $123, sFr123
- * @return the prefix
- * @stable ICU 2.0
- */
- public String getPositivePrefix() {
- return positivePrefix;
+ /**
+ * @return The character used for padding.
+ * @see #setPadCharacter
+ * @category Padding
+ * @stable ICU 2.0
+ */
+ public synchronized char getPadCharacter() {
+ CharSequence paddingString = exportedProperties.getPadString();
+ if (paddingString == null) {
+ return '.'; // TODO: Is this the correct behavior?
+ } else {
+ return paddingString.charAt(0);
}
+ }
- /**
- * Sets the positive prefix.
- *
- * Examples: +123, $123, sFr123
- * @param newValue the prefix
- * @stable ICU 2.0
- */
- public void setPositivePrefix(String newValue) {
- positivePrefix = newValue;
- posPrefixPattern = null;
- }
+ /**
+ * Padding: Sets the character used to pad numbers that are narrower than the
+ * width specified in {@link #setFormatWidth}.
+ *
+ * In the pattern string, the padding character is the token that follows '*' before or after
+ * the prefix or suffix.
+ *
+ * @param padChar The character used for padding.
+ * @see #setFormatWidth
+ * @category Padding
+ * @stable ICU 2.0
+ */
+ public synchronized void setPadCharacter(char padChar) {
+ properties.setPadString(Character.toString(padChar));
+ refreshFormatter();
+ }
- /**
- * Returns the negative prefix.
- *
- * Examples: -123, ($123) (with negative suffix), sFr-123
- *
- * @return the prefix
- * @stable ICU 2.0
- */
- public String getNegativePrefix() {
- return negativePrefix;
- }
+ /**
+ * @return The position used for padding.
+ * @see #setPadPosition
+ * @category Padding
+ * @stable ICU 2.0
+ */
+ public synchronized int getPadPosition() {
+ PadPosition loc = exportedProperties.getPadPosition();
+ return (loc == null) ? PAD_BEFORE_PREFIX : loc.toOld();
+ }
- /**
- * Sets the negative prefix.
- *
- * Examples: -123, ($123) (with negative suffix), sFr-123
- * @param newValue the prefix
- * @stable ICU 2.0
- */
- public void setNegativePrefix(String newValue) {
- negativePrefix = newValue;
- negPrefixPattern = null;
- }
+ /**
+ * Padding: Sets the position where to insert the pad character when narrower
+ * than the width specified in {@link #setFormatWidth}. For example, consider the pattern "P123S"
+ * with padding width 8 and padding char "*". The four positions are:
+ *
+ * Example: 123%
- *
- * @return the suffix
- * @stable ICU 2.0
- */
- public String getPositiveSuffix() {
- return positiveSuffix;
- }
+ /**
+ * @return Whether scientific (exponential) notation is enabled on this formatter.
+ * @see #setScientificNotation
+ * @category ScientificNotation
+ * @stable ICU 2.0
+ */
+ public synchronized boolean isScientificNotation() {
+ return ScientificFormat.useScientificNotation(properties);
+ }
- /**
- * Sets the positive suffix.
- *
- * Example: 123%
- * @param newValue the suffix
- * @stable ICU 2.0
- */
- public void setPositiveSuffix(String newValue) {
- positiveSuffix = newValue;
- posSuffixPattern = null;
+ /**
+ * Scientific Notation: Sets whether this formatter should print in scientific
+ * (exponential) notation. For example, if scientific notation is enabled, the number 123000 will
+ * be printed as "1.23E5" in locale en-US. A locale-specific symbol is used as the
+ * exponent separator.
+ *
+ * Calling Examples: -123%, ($123) (with positive suffixes)
- *
- * @return the suffix
- * @stable ICU 2.0
- */
- public String getNegativeSuffix() {
- return negativeSuffix;
- }
+ /**
+ * @return The minimum number of digits printed in the exponent in scientific notation.
+ * @see #setMinimumExponentDigits
+ * @category ScientificNotation
+ * @stable ICU 2.0
+ */
+ public synchronized byte getMinimumExponentDigits() {
+ return (byte) exportedProperties.getMinimumExponentDigits();
+ }
- /**
- * Sets the positive suffix.
- *
- * Examples: 123%
- * @param newValue the suffix
- * @stable ICU 2.0
- */
- public void setNegativeSuffix(String newValue) {
- negativeSuffix = newValue;
- negSuffixPattern = null;
- }
+ /**
+ * Scientific Notation: Sets the minimum number of digits to be printed in the
+ * exponent. For example, if minimum exponent digits is 3, the number 123000 will be printed as
+ * "1.23E005".
+ *
+ * This setting corresponds to the number of zeros after the 'E' in a pattern string such as
+ * "0.00E000".
+ *
+ * @param minExpDig The minimum number of digits in the exponent.
+ * @category ScientificNotation
+ * @stable ICU 2.0
+ */
+ public synchronized void setMinimumExponentDigits(byte minExpDig) {
+ properties.setMinimumExponentDigits(minExpDig);
+ refreshFormatter();
+ }
- /**
- * Returns the multiplier for use in percent, permill, etc. For a percentage, set the
- * suffixes to have "%" and the multiplier to be 100. (For Arabic, use arabic percent
- * symbol). For a permill, set the suffixes to have "\u2031" and the multiplier to be
- * 1000.
- *
- * Examples: with 100, 1.23 -> "123", and "123" -> 1.23
- *
- * @return the multiplier
- * @stable ICU 2.0
- */
- public int getMultiplier() {
- return multiplier;
- }
+ /**
+ * @return Whether the sign (plus or minus) is always printed in scientific notation.
+ * @see #setExponentSignAlwaysShown
+ * @category ScientificNotation
+ * @stable ICU 2.0
+ */
+ public synchronized boolean isExponentSignAlwaysShown() {
+ return exportedProperties.getExponentSignAlwaysShown();
+ }
- /**
- * Sets the multiplier for use in percent, permill, etc. For a percentage, set the
- * suffixes to have "%" and the multiplier to be 100. (For Arabic, use arabic percent
- * symbol). For a permill, set the suffixes to have "\u2031" and the multiplier to be
- * 1000.
- *
- * Examples: with 100, 1.23 -> "123", and "123" -> 1.23
- *
- * @param newValue the multiplier
- * @stable ICU 2.0
- */
- public void setMultiplier(int newValue) {
- if (newValue == 0) {
- throw new IllegalArgumentException("Bad multiplier: " + newValue);
- }
- multiplier = newValue;
- }
+ /**
+ * Scientific Notation: Sets whether the sign (plus or minus) is always to be
+ * shown in the exponent in scientific notation. For example, if this setting is enabled, the
+ * number 123000 will be printed as "1.23E+5" in locale en-US. The number 0.0000123 will
+ * always be printed as "1.23E-5" in locale en-US whether or not this setting is enabled.
+ *
+ * This setting corresponds to the '+' in a pattern such as "0.00E+0".
+ *
+ * @param expSignAlways true to always shown the sign in the exponent; false to show it for
+ * negatives but not positives.
+ * @category ScientificNotation
+ * @stable ICU 2.0
+ */
+ public synchronized void setExponentSignAlwaysShown(boolean expSignAlways) {
+ properties.setExponentSignAlwaysShown(expSignAlways);
+ refreshFormatter();
+ }
- /**
- * {@icu} Returns the rounding increment.
- *
- * @return A positive rounding increment, or For example, if grouping is enabled, 12345 will be printed as "12,345" in en-US. If
+ * grouping were disabled, it would instead be printed as simply "12345".
+ *
+ * Calling For example, with a grouping size of 3, the number 1234567 will be formatted as "1,234,567".
+ *
+ * Grouping size can also be specified in the pattern: for example, "#,##0" corresponds to a
+ * grouping size of 3.
+ *
+ * @param width The grouping size to use.
+ * @see #setSecondaryGroupingSize
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ public synchronized void setGroupingSize(int width) {
+ properties.setGroupingSize(width);
+ refreshFormatter();
+ }
- /**
- * Returns the rounding mode.
- *
- * @return A rounding mode, between For example, with primary grouping size 3 and secondary grouping size 2, the number 1234567
+ * will be formatted as "12,34,567".
+ *
+ * Grouping size can also be specified in the pattern: for example, "#,##,##0" corresponds to a
+ * primary grouping size of 3 and a secondary grouping size of 2.
+ *
+ * @param width The secondary grouping size to use.
+ * @see #setGroupingSize
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ public synchronized void setSecondaryGroupingSize(int width) {
+ properties.setSecondaryGroupingSize(width);
+ refreshFormatter();
+ }
- this.roundingMode = roundingMode;
- resetActualRounding();
- }
+ /**
+ * @return The minimum number of digits before grouping is triggered.
+ * @see #setMinimumGroupingDigits
+ * @category Separators
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized int getMinimumGroupingDigits() {
+ return properties.getMinimumGroupingDigits();
+ }
- /**
- * Returns the width to which the output of This setting can be specified in the pattern for integer formats: "#,##0." is an example.
+ *
+ * @param value true to always show the decimal separator; false to show it only when there is a
+ * fraction part of the number.
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ public synchronized void setDecimalSeparatorAlwaysShown(boolean value) {
+ properties.setDecimalSeparatorAlwaysShown(value);
+ refreshFormatter();
+ }
- /**
- * {@icu} Sets the character used to pad to the format width. If padding is not
- * enabled, then this will take effect if padding is later enabled.
- *
- * @param padChar the pad character
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #getPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public void setPadCharacter(char padChar) {
- pad = padChar;
- }
+ /**
+ * @return The user-specified currency. May be null.
+ * @see #setCurrency
+ * @see DecimalFormatSymbols#getCurrency
+ * @category Currency
+ * @stable ICU 2.6
+ */
+ @Override
+ public synchronized Currency getCurrency() {
+ return properties.getCurrency();
+ }
- /**
- * {@icu} Returns the position at which padding will take place. This is the location at
- * which padding will be inserted if the result of Most users should not call this method directly. You should instead create
+ * your formatter via For example, in en-US, parsing the string "123.45" will return the number 123 and
+ * parse position 3.
+ *
+ * This is functionally equivalent to calling {@link #setDecimalPatternMatchRequired} and a
+ * pattern without a decimal point.
+ *
+ * @param parseIntegerOnly true to ignore fractional parts of numbers when parsing; false to
+ * consume fractional parts.
+ * @category Parsing
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setParseIntegerOnly(boolean parseIntegerOnly) {
+ properties.setParseIntegerOnly(parseIntegerOnly);
+ // refreshFormatter() not needed
+ }
- /**
- * {@icu} Returns the MathContext used by this format.
- *
- * @return desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public java.math.MathContext getMathContext() {
- try {
- // don't allow multiple references
- return mathContext == null ? null : new java.math.MathContext(mathContext.getDigits(),
- java.math.RoundingMode.valueOf(mathContext.getRoundingMode()));
- } catch (Exception foo) {
- return null; // should never happen
- }
- }
+ /**
+ * @return Whether the presence of a decimal point must match the pattern.
+ * @see #setDecimalPatternMatchRequired
+ * @category Parsing
+ * @stable ICU 54
+ */
+ public synchronized boolean isDecimalPatternMatchRequired() {
+ return properties.getDecimalPatternMatchRequired();
+ }
- /**
- * {@icu} Sets the MathContext used by this format.
- *
- * @param newValue desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public void setMathContextICU(MathContext newValue) {
- mathContext = newValue;
- }
+ /**
+ * Parsing: This method is used to either require or forbid the
+ * presence of a decimal point in the string being parsed (disabled by default). This feature was
+ * designed to be an extra layer of strictness on top of strict parsing, although it can be used
+ * in either lenient mode or strict mode.
+ *
+ * To require a decimal point, call this method in combination with either a pattern
+ * containing a decimal point or with {@link #setDecimalSeparatorAlwaysShown}.
+ *
+ * Example: Decimal ON: 12345 ->
- * 12345.; OFF: 12345 -> 12345
- *
- * @stable ICU 2.0
- */
- public boolean isDecimalSeparatorAlwaysShown() {
- return decimalSeparatorAlwaysShown;
- }
+ /**
+ * Specifies whether to stop parsing when an exponent separator is encountered. For example,
+ * parses "123E4" to 123 (with parse position 3) instead of 1230000 (with parse position 5).
+ *
+ * @param value true to prevent exponents from being parsed; false to allow them to be parsed.
+ * @category Parsing
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized void setParseNoExponent(boolean value) {
+ properties.setParseNoExponent(value);
+ refreshFormatter();
+ }
- /**
- * When decimal match is not required, the input does not have to
- * contain a decimal mark when there is a decimal mark specified in the
- * pattern.
- * @param value true if input must contain a match to decimal mark in pattern
- * Default is false.
- * @stable ICU 54
- */
- public void setDecimalPatternMatchRequired(boolean value) {
- parseRequireDecimalPoint = value;
- }
+ /**
+ * @return Whether to force case (uppercase/lowercase) to match when parsing.
+ * @see #setParseNoExponent
+ * @category Parsing
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized boolean getParseCaseSensitive() {
+ return properties.getParseCaseSensitive();
+ }
- /**
- * {@icu} Returns whether the input to parsing must contain a decimal mark if there
- * is a decimal mark in the pattern.
- * @return true if input must contain a match to decimal mark in pattern
- * @stable ICU 54
- */
- public boolean isDecimalPatternMatchRequired() {
- return parseRequireDecimalPoint;
- }
+ /**
+ * Specifies whether parsing should require cases to match in affixes, exponent separators, and
+ * currency codes. Case mapping is performed for each code point using {@link
+ * UCharacter#foldCase}.
+ *
+ * @param value true to force case (uppercase/lowercase) to match when parsing; false to ignore
+ * case and perform case folding.
+ * @category Parsing
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized void setParseCaseSensitive(boolean value) {
+ properties.setParseCaseSensitive(value);
+ refreshFormatter();
+ }
+ //=====================================================================================//
+ // UTILITIES //
+ //=====================================================================================//
- /**
- * Sets the behavior of the decimal separator with integers. (The decimal separator
- * will always appear with decimals.)
- *
- * This only affects formatting, and only where there might be no digits after the
- * decimal point, e.g., if true, 3456.00 -> "3,456." if false, 3456.00 -> "3456" This
- * is independent of parsing. If you want parsing to stop at the decimal point, use
- * setParseIntegerOnly.
- *
- *
- * Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345
- *
- * @stable ICU 2.0
- */
- public void setDecimalSeparatorAlwaysShown(boolean newValue) {
- decimalSeparatorAlwaysShown = newValue;
- }
+ /**
+ * Tests for equality between this formatter and another formatter.
+ *
+ * If two DecimalFormat instances are equal, then they will always produce the same output.
+ * However, the reverse is not necessarily true: if two DecimalFormat instances always produce the
+ * same output, they are not necessarily equal.
+ *
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (obj == this) return true;
+ if (!(obj instanceof DecimalFormat)) return false;
+ DecimalFormat other = (DecimalFormat) obj;
+ return properties.equals(other.properties) && symbols.equals(other.symbols);
+ }
- /**
- * {@icu} Returns a copy of the CurrencyPluralInfo used by this format. It might
- * return null if the decimal format is not a plural type currency decimal
- * format. Plural type currency decimal format means either the pattern in the decimal
- * format contains 3 currency signs, or the decimal format is initialized with
- * PLURALCURRENCYSTYLE.
- *
- * @return desired CurrencyPluralInfo
- * @see CurrencyPluralInfo
- * @stable ICU 4.2
- */
- public CurrencyPluralInfo getCurrencyPluralInfo() {
- try {
- // don't allow multiple references
- return currencyPluralInfo == null ? null :
- (CurrencyPluralInfo) currencyPluralInfo.clone();
- } catch (Exception foo) {
- return null; // should never happen
- }
- }
+ /** @stable ICU 2.0 */
+ @Override
+ public synchronized int hashCode() {
+ return properties.hashCode();
+ }
- /**
- * {@icu} Sets the CurrencyPluralInfo used by this format. The format uses a copy of
- * the provided information.
- *
- * @param newInfo desired CurrencyPluralInfo
- * @see CurrencyPluralInfo
- * @stable ICU 4.2
- */
- public void setCurrencyPluralInfo(CurrencyPluralInfo newInfo) {
- currencyPluralInfo = (CurrencyPluralInfo) newInfo.clone();
- isReadyForParsing = false;
- }
-
- /**
- * Overrides clone.
- * @stable ICU 2.0
- */
- @Override
- public Object clone() {
- try {
- DecimalFormat other = (DecimalFormat) super.clone();
- other.symbols = (DecimalFormatSymbols) symbols.clone();
- other.digitList = new DigitList(); // fix for JB#5358
- if (currencyPluralInfo != null) {
- other.currencyPluralInfo = (CurrencyPluralInfo) currencyPluralInfo.clone();
- }
- other.attributes = new ArrayList For more information on decimal format pattern strings, see UTS #35.
+ *
+ * Important: Not all properties are capable of being encoded in a pattern
+ * string. See a list of properties in {@link #applyPattern}.
+ *
+ * @return A decimal format pattern string.
+ * @stable ICU 2.0
+ */
+ public synchronized String toPattern() {
+ // Pull some properties from exportedProperties and others from properties
+ // to keep affix patterns intact. In particular, pull rounding properties
+ // so that CurrencyUsage is reflected properly.
+ // TODO: Consider putting this logic in PatternString.java instead.
+ Properties tprops = threadLocalToPatternProperties.get();
+ tprops.copyFrom(properties);
+ if (com.ibm.icu.impl.number.formatters.CurrencyFormat.useCurrency(properties)) {
+ tprops.setMinimumFractionDigits(exportedProperties.getMinimumFractionDigits());
+ tprops.setMaximumFractionDigits(exportedProperties.getMaximumFractionDigits());
+ tprops.setRoundingIncrement(exportedProperties.getRoundingIncrement());
}
+ return PatternString.propertiesToString(tprops);
+ }
- // method to unquote the strings and compare
- private boolean equals(String pat1, String pat2) {
- if (pat1 == null || pat2 == null) {
- return (pat1 == null && pat2 == null);
- }
- // fast path
- if (pat1.equals(pat2)) {
- return true;
- }
- return unquote(pat1).equals(unquote(pat2));
- }
+ /**
+ * Calls {@link #toPattern} and converts the string to localized notation. For more information on
+ * localized notation, see {@link #applyLocalizedPattern}.
+ *
+ * @return A decimal format pattern string in localized notation.
+ * @stable ICU 2.0
+ */
+ public synchronized String toLocalizedPattern() {
+ String pattern = toPattern();
+ return PatternString.convertLocalized(pattern, symbols, true);
+ }
- private String unquote(String pat) {
- StringBuilder buf = new StringBuilder(pat.length());
- int i = 0;
- while (i < pat.length()) {
- char ch = pat.charAt(i++);
- if (ch != QUOTE) {
- buf.append(ch);
- }
- }
- return buf.toString();
- }
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public IFixedDecimal getFixedDecimal(double number) {
+ FormatQuantity4 fq = new FormatQuantity4(number);
+ formatter.format(fq);
+ return fq;
+ }
- // protected void handleToString(StringBuffer buf) {
- // buf.append("\nposPrefixPattern: '" + posPrefixPattern + "'\n");
- // buf.append("positivePrefix: '" + positivePrefix + "'\n");
- // buf.append("posSuffixPattern: '" + posSuffixPattern + "'\n");
- // buf.append("positiveSuffix: '" + positiveSuffix + "'\n");
- // buf.append("negPrefixPattern: '" +
- // com.ibm.icu.impl.Utility.format1ForSource(negPrefixPattern) + "'\n");
- // buf.append("negativePrefix: '" +
- // com.ibm.icu.impl.Utility.format1ForSource(negativePrefix) + "'\n");
- // buf.append("negSuffixPattern: '" + negSuffixPattern + "'\n");
- // buf.append("negativeSuffix: '" + negativeSuffix + "'\n");
- // buf.append("multiplier: '" + multiplier + "'\n");
- // buf.append("groupingSize: '" + groupingSize + "'\n");
- // buf.append("groupingSize2: '" + groupingSize2 + "'\n");
- // buf.append("decimalSeparatorAlwaysShown: '" + decimalSeparatorAlwaysShown + "'\n");
- // buf.append("useExponentialNotation: '" + useExponentialNotation + "'\n");
- // buf.append("minExponentDigits: '" + minExponentDigits + "'\n");
- // buf.append("useSignificantDigits: '" + useSignificantDigits + "'\n");
- // buf.append("minSignificantDigits: '" + minSignificantDigits + "'\n");
- // buf.append("maxSignificantDigits: '" + maxSignificantDigits + "'\n");
- // buf.append("symbols: '" + symbols + "'");
- // }
-
- /**
- * Overrides hashCode.
- * @stable ICU 2.0
- */
- @Override
- public int hashCode() {
- return super.hashCode() * 37 + positivePrefix.hashCode();
- // just enough fields for a reasonable distribution
+ /** Rebuilds the formatter object from the property bag. */
+ void refreshFormatter() {
+ if (exportedProperties == null) {
+ // exportedProperties is null only when the formatter is not ready yet.
+ // The only time when this happens is during legacy deserialization.
+ return;
}
+ formatter = Endpoint.fromBTA(properties, symbols);
+ exportedProperties.clear();
+ formatter.export(exportedProperties);
+ }
- /**
- * Synthesizes a pattern string that represents the current state of this Format
- * object.
- *
- * @see #applyPattern
- * @stable ICU 2.0
- */
- public String toPattern() {
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- // the prefix or suffix pattern might not be defined yet, so they can not be
- // synthesized, instead, get them directly. but it might not be the actual
- // pattern used in formatting. the actual pattern used in formatting depends
- // on the formatted number's plural count.
- return formatPattern;
- }
- return toPattern(false);
- }
+ /**
+ * Updates the property bag with settings from the given pattern.
+ *
+ * @param pattern The pattern string to parse.
+ * @param ignoreRounding Whether to read rounding information from the string. Set to false if
+ * CurrencyUsage is to be used instead.
+ * @see PatternString#parseToExistingProperties
+ */
+ void setPropertiesFromPattern(String pattern, boolean ignoreRounding) {
+ PatternString.parseToExistingProperties(pattern, properties, ignoreRounding);
+ }
- /**
- * Synthesizes a localized pattern string that represents the current state of this
- * Format object.
- *
- * @see #applyPattern
- * @stable ICU 2.0
- */
- public String toLocalizedPattern() {
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- return formatPattern;
- }
- return toPattern(true);
- }
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public synchronized void setProperties(PropertySetter func) {
+ func.set(properties);
+ refreshFormatter();
+ }
- /**
- * Expands the affix pattern strings into the expanded affix strings. If any affix
- * pattern string is null, do not expand it. This method should be called any time the
- * symbols or the affix patterns change in order to keep the expanded affix strings up
- * to date. This method also will be called before formatting if format currency
- * plural names, since the plural name is not a static one, it is based on the
- * currency plural count, the affix will be known only after the currency plural count
- * is know. In which case, the parameter 'pluralCount' will be a non-null currency
- * plural count. In all other cases, the 'pluralCount' is null, which means it is not
- * needed.
- */
- // Bug 4212072 [Richard/GCL]
- private void expandAffixes(String pluralCount) {
- // expandAffix() will set currencyChoice to a non-null value if
- // appropriate AND if it is null.
- currencyChoice = null;
+ public static interface PropertySetter {
+ public void set(Properties props);
+ }
- // Reuse one StringBuffer for better performance
- StringBuffer buffer = new StringBuffer();
- if (posPrefixPattern != null) {
- expandAffix(posPrefixPattern, pluralCount, buffer);
- positivePrefix = buffer.toString();
- }
- if (posSuffixPattern != null) {
- expandAffix(posSuffixPattern, pluralCount, buffer);
- positiveSuffix = buffer.toString();
- }
- if (negPrefixPattern != null) {
- expandAffix(negPrefixPattern, pluralCount, buffer);
- negativePrefix = buffer.toString();
- }
- if (negSuffixPattern != null) {
- expandAffix(negSuffixPattern, pluralCount, buffer);
- negativeSuffix = buffer.toString();
- }
- }
+ /**
+ * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to specify pad
+ * characters inserted before the prefix.
+ *
+ * @see #setPadPosition
+ * @see #getPadPosition
+ * @see #PAD_AFTER_PREFIX
+ * @see #PAD_BEFORE_SUFFIX
+ * @see #PAD_AFTER_SUFFIX
+ * @stable ICU 2.0
+ */
+ public static final int PAD_BEFORE_PREFIX = 0;
- /**
- * Expands an affix pattern into an affix string. All characters in the pattern are
- * literal unless bracketed by QUOTEs. The following characters outside QUOTE are
- * recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, PATTERN_MINUS, and
- * CURRENCY_SIGN. If CURRENCY_SIGN is doubled, it is interpreted as an international
- * currency sign. If CURRENCY_SIGN is tripled, it is interpreted as currency plural
- * long names, such as "US Dollars". Any other character outside QUOTE represents
- * itself. Quoted text must be well-formed.
- *
- * This method is used in two distinct ways. First, it is used to expand the stored
- * affix patterns into actual affixes. For this usage, doFormat must be false. Second,
- * it is used to expand the stored affix patterns given a specific number (doFormat ==
- * true), for those rare cases in which a currency format references a ChoiceFormat
- * (e.g., en_IN display name for INR). The number itself is taken from digitList.
- * TODO: There are no currency ChoiceFormat patterns, figure out what is still relevant here.
- *
- * When used in the first way, this method has a side effect: It sets currencyChoice
- * to a ChoiceFormat object, if the currency's display name in this locale is a
- * ChoiceFormat pattern (very rare). It only does this if currencyChoice is null to
- * start with.
- *
- * @param pattern the non-null, possibly empty pattern
- * @param pluralCount the plural count. It is only used for currency plural format. In
- * which case, it is the plural count of the currency amount. For example, in en_US,
- * it is the singular "one", or the plural "other". For all other cases, it is null,
- * and is not being used.
- * @param buffer a scratch StringBuffer; its contents will be lost
- */
- // Bug 4212072 [Richard/GCL]
- private void expandAffix(String pattern, String pluralCount, StringBuffer buffer) {
- buffer.setLength(0);
- for (int i = 0; i < pattern.length();) {
- char c = pattern.charAt(i++);
- if (c == QUOTE) {
- for (;;) {
- int j = pattern.indexOf(QUOTE, i);
- if (j == i) {
- buffer.append(QUOTE);
- i = j + 1;
- break;
- } else if (j > i) {
- buffer.append(pattern.substring(i, j));
- i = j + 1;
- if (i < pattern.length() && pattern.charAt(i) == QUOTE) {
- buffer.append(QUOTE);
- ++i;
- // loop again
- } else {
- break;
- }
- } else {
- // Unterminated quote; should be caught by apply
- // pattern.
- throw new RuntimeException();
- }
- }
- continue;
- }
+ /**
+ * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to specify pad
+ * characters inserted after the prefix.
+ *
+ * @see #setPadPosition
+ * @see #getPadPosition
+ * @see #PAD_BEFORE_PREFIX
+ * @see #PAD_BEFORE_SUFFIX
+ * @see #PAD_AFTER_SUFFIX
+ * @stable ICU 2.0
+ */
+ public static final int PAD_AFTER_PREFIX = 1;
- switch (c) {
- case CURRENCY_SIGN:
- // As of ICU 2.2 we use the currency object, and ignore the currency
- // symbols in the DFS, unless we have a null currency object. This occurs
- // if resurrecting a pre-2.2 object or if the user sets a custom DFS.
- boolean intl = i < pattern.length() && pattern.charAt(i) == CURRENCY_SIGN;
- boolean plural = false;
- if (intl) {
- ++i;
- if (i < pattern.length() && pattern.charAt(i) == CURRENCY_SIGN) {
- plural = true;
- intl = false;
- ++i;
- }
- }
- String s = null;
- Currency currency = getCurrency();
- if (currency != null) {
- // plural name is only needed when pluralCount != null, which means
- // when formatting currency plural names. For other cases,
- // pluralCount == null, and plural names are not needed.
- if (plural && pluralCount != null) {
- s = currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME,
- pluralCount, null);
- } else if (!intl) {
- s = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
- } else {
- s = currency.getCurrencyCode();
- }
- } else {
- s = intl ? symbols.getInternationalCurrencySymbol() :
- symbols.getCurrencySymbol();
- }
- // Here is where FieldPosition could be set for CURRENCY PLURAL.
- buffer.append(s);
- break;
- case PATTERN_PERCENT:
- buffer.append(symbols.getPercentString());
- break;
- case PATTERN_PER_MILLE:
- buffer.append(symbols.getPerMillString());
- break;
- case PATTERN_MINUS_SIGN:
- buffer.append(symbols.getMinusSignString());
- break;
- default:
- buffer.append(c);
- break;
- }
- }
- }
+ /**
+ * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to specify pad
+ * characters inserted before the suffix.
+ *
+ * @see #setPadPosition
+ * @see #getPadPosition
+ * @see #PAD_BEFORE_PREFIX
+ * @see #PAD_AFTER_PREFIX
+ * @see #PAD_AFTER_SUFFIX
+ * @stable ICU 2.0
+ */
+ public static final int PAD_BEFORE_SUFFIX = 2;
- /**
- * Append an affix to the given StringBuffer.
- *
- * @param buf
- * buffer to append to
- * @param isNegative
- * @param isPrefix
- * @param fieldPosition
- * @param parseAttr
- */
- private int appendAffix(StringBuffer buf, boolean isNegative, boolean isPrefix,
- FieldPosition fieldPosition,
- boolean parseAttr) {
- if (currencyChoice != null) {
- String affixPat = null;
- if (isPrefix) {
- affixPat = isNegative ? negPrefixPattern : posPrefixPattern;
- } else {
- affixPat = isNegative ? negSuffixPattern : posSuffixPattern;
- }
- StringBuffer affixBuf = new StringBuffer();
- expandAffix(affixPat, null, affixBuf);
- buf.append(affixBuf);
- return affixBuf.length();
- }
-
- String affix = null;
- String pattern;
- if (isPrefix) {
- affix = isNegative ? negativePrefix : positivePrefix;
- pattern = isNegative ? negPrefixPattern : posPrefixPattern;
- } else {
- affix = isNegative ? negativeSuffix : positiveSuffix;
- pattern = isNegative ? negSuffixPattern : posSuffixPattern;
- }
- // [Spark/CDL] Invoke formatAffix2Attribute to add attributes for affix
- if (parseAttr) {
- // Updates for Ticket 11805.
- int offset = affix.indexOf(symbols.getCurrencySymbol());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.CURRENCY, buf, offset,
- symbols.getCurrencySymbol().length());
- }
- offset = affix.indexOf(symbols.getMinusSignString());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.SIGN, buf, offset,
- symbols.getMinusSignString().length());
- }
- offset = affix.indexOf(symbols.getPercentString());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.PERCENT, buf, offset,
- symbols.getPercentString().length());
- }
- offset = affix.indexOf(symbols.getPerMillString());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.PERMILLE, buf, offset,
- symbols.getPerMillString().length());
- }
- offset = pattern.indexOf("¤¤¤");
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.CURRENCY, buf, offset,
- affix.length() - offset);
- }
- }
-
- // Look for SIGN, PERCENT, PERMILLE in the formatted affix.
- if (fieldPosition.getFieldAttribute() == NumberFormat.Field.SIGN) {
- String sign = isNegative ? symbols.getMinusSignString() : symbols.getPlusSignString();
- int firstPos = affix.indexOf(sign);
- if (firstPos > -1) {
- int startPos = buf.length() + firstPos;
- fieldPosition.setBeginIndex(startPos);
- fieldPosition.setEndIndex(startPos + sign.length());
- }
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.PERCENT) {
- int firstPos = affix.indexOf(symbols.getPercentString());
- if (firstPos > -1) {
- int startPos = buf.length() + firstPos;
- fieldPosition.setBeginIndex(startPos);
- fieldPosition.setEndIndex(startPos + symbols.getPercentString().length());
- }
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.PERMILLE) {
- int firstPos = affix.indexOf(symbols.getPerMillString());
- if (firstPos > -1) {
- int startPos = buf.length() + firstPos;
- fieldPosition.setBeginIndex(startPos);
- fieldPosition.setEndIndex(startPos + symbols.getPerMillString().length());
- }
- } else
- // If CurrencySymbol or InternationalCurrencySymbol is in the affix, check for currency symbol.
- // Get spelled out name if "¤¤¤" is in the pattern.
- if (fieldPosition.getFieldAttribute() == NumberFormat.Field.CURRENCY) {
- if (affix.indexOf(symbols.getCurrencySymbol()) > -1) {
- String aff = symbols.getCurrencySymbol();
- int firstPos = affix.indexOf(aff);
- int start = buf.length() + firstPos;
- int end = start + aff.length();
- fieldPosition.setBeginIndex(start);
- fieldPosition.setEndIndex(end);
- } else if (affix.indexOf(symbols.getInternationalCurrencySymbol()) > -1) {
- String aff = symbols.getInternationalCurrencySymbol();
- int firstPos = affix.indexOf(aff);
- int start = buf.length() + firstPos;
- int end = start + aff.length();
- fieldPosition.setBeginIndex(start);
- fieldPosition.setEndIndex(end);
- } else if (pattern.indexOf("¤¤¤") > -1) {
- // It's a plural, and we know where it is in the pattern.
- int firstPos = pattern.indexOf("¤¤¤");
- int start = buf.length() + firstPos;
- int end = buf.length() + affix.length(); // This seems clunky and wrong.
- fieldPosition.setBeginIndex(start);
- fieldPosition.setEndIndex(end);
- }
- }
-
- buf.append(affix);
- return affix.length();
- }
-
- // Fix for prefix and suffix in Ticket 11805.
- private void formatAffix2Attribute(boolean isPrefix, Field fieldType,
- StringBuffer buf, int offset, int symbolSize) {
- int begin;
- begin = offset;
- if (!isPrefix) {
- begin += buf.length();
- }
-
- addAttribute(fieldType, begin, begin + symbolSize);
- }
-
- /**
- * [Spark/CDL] Use this method to add attribute.
- */
- private void addAttribute(Field field, int begin, int end) {
- FieldPosition pos = new FieldPosition(field);
- pos.setBeginIndex(begin);
- pos.setEndIndex(end);
- attributes.add(pos);
- }
-
- /**
- * Formats the object to an attributed string, and return the corresponding iterator.
- *
- * @stable ICU 3.6
- */
- @Override
- public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
- return formatToCharacterIterator(obj, NULL_UNIT);
- }
-
- AttributedCharacterIterator formatToCharacterIterator(Object obj, Unit unit) {
- if (!(obj instanceof Number))
- throw new IllegalArgumentException();
- Number number = (Number) obj;
- StringBuffer text = new StringBuffer();
- unit.writePrefix(text);
- attributes.clear();
- if (obj instanceof BigInteger) {
- format((BigInteger) number, text, new FieldPosition(0), true);
- } else if (obj instanceof java.math.BigDecimal) {
- format((java.math.BigDecimal) number, text, new FieldPosition(0)
- , true);
- } else if (obj instanceof Double) {
- format(number.doubleValue(), text, new FieldPosition(0), true);
- } else if (obj instanceof Integer || obj instanceof Long) {
- format(number.longValue(), text, new FieldPosition(0), true);
- } else {
- throw new IllegalArgumentException();
- }
- unit.writeSuffix(text);
- AttributedString as = new AttributedString(text.toString());
-
- // add NumberFormat field attributes to the AttributedString
- for (int i = 0; i < attributes.size(); i++) {
- FieldPosition pos = attributes.get(i);
- Format.Field attribute = pos.getFieldAttribute();
- as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos.getEndIndex());
- }
-
- // return the CharacterIterator from AttributedString
- return as.getIterator();
- }
-
- /**
- * Appends an affix pattern to the given StringBuffer. Localize unquoted specials.
- *
- * Note: This implementation does not support new String localized symbols.
- */
- private void appendAffixPattern(StringBuffer buffer, boolean isNegative, boolean isPrefix,
- boolean localized) {
- String affixPat = null;
- if (isPrefix) {
- affixPat = isNegative ? negPrefixPattern : posPrefixPattern;
- } else {
- affixPat = isNegative ? negSuffixPattern : posSuffixPattern;
- }
-
- // When there is a null affix pattern, we use the affix itself.
- if (affixPat == null) {
- String affix = null;
- if (isPrefix) {
- affix = isNegative ? negativePrefix : positivePrefix;
- } else {
- affix = isNegative ? negativeSuffix : positiveSuffix;
- }
- // Do this crudely for now: Wrap everything in quotes.
- buffer.append(QUOTE);
- for (int i = 0; i < affix.length(); ++i) {
- char ch = affix.charAt(i);
- if (ch == QUOTE) {
- buffer.append(ch);
- }
- buffer.append(ch);
- }
- buffer.append(QUOTE);
- return;
- }
-
- if (!localized) {
- buffer.append(affixPat);
- } else {
- int i, j;
- for (i = 0; i < affixPat.length(); ++i) {
- char ch = affixPat.charAt(i);
- switch (ch) {
- case QUOTE:
- j = affixPat.indexOf(QUOTE, i + 1);
- if (j < 0) {
- throw new IllegalArgumentException("Malformed affix pattern: " + affixPat);
- }
- buffer.append(affixPat.substring(i, j + 1));
- i = j;
- continue;
- case PATTERN_PER_MILLE:
- ch = symbols.getPerMill();
- break;
- case PATTERN_PERCENT:
- ch = symbols.getPercent();
- break;
- case PATTERN_MINUS_SIGN:
- ch = symbols.getMinusSign();
- break;
- }
- // check if char is same as any other symbol
- if (ch == symbols.getDecimalSeparator() || ch == symbols.getGroupingSeparator()) {
- buffer.append(QUOTE);
- buffer.append(ch);
- buffer.append(QUOTE);
- } else {
- buffer.append(ch);
- }
- }
- }
- }
-
- /**
- * Does the real work of generating a pattern.
- *
- * Note: This implementation does not support new String localized symbols.
- */
- private String toPattern(boolean localized) {
- StringBuffer result = new StringBuffer();
- char zero = localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT;
- char digit = localized ? symbols.getDigit() : PATTERN_DIGIT;
- char sigDigit = 0;
- boolean useSigDig = areSignificantDigitsUsed();
- if (useSigDig) {
- sigDigit = localized ? symbols.getSignificantDigit() : PATTERN_SIGNIFICANT_DIGIT;
- }
- char group = localized ? symbols.getGroupingSeparator() : PATTERN_GROUPING_SEPARATOR;
- int i;
- int roundingDecimalPos = 0; // Pos of decimal in roundingDigits
- String roundingDigits = null;
- int padPos = (formatWidth > 0) ? padPosition : -1;
- String padSpec = (formatWidth > 0)
- ? new StringBuffer(2).append(localized
- ? symbols.getPadEscape()
- : PATTERN_PAD_ESCAPE).append(pad).toString()
- : null;
- if (roundingIncrementICU != null) {
- i = roundingIncrementICU.scale();
- roundingDigits = roundingIncrementICU.movePointRight(i).toString();
- roundingDecimalPos = roundingDigits.length() - i;
- }
- for (int part = 0; part < 2; ++part) {
- // variable not used int partStart = result.length();
- if (padPos == PAD_BEFORE_PREFIX) {
- result.append(padSpec);
- }
-
- // Use original symbols read from resources in pattern eg. use "\u00A4"
- // instead of "$" in Locale.US [Richard/GCL]
- appendAffixPattern(result, part != 0, true, localized);
- if (padPos == PAD_AFTER_PREFIX) {
- result.append(padSpec);
- }
- int sub0Start = result.length();
- int g = isGroupingUsed() ? Math.max(0, groupingSize) : 0;
- if (g > 0 && groupingSize2 > 0 && groupingSize2 != groupingSize) {
- g += groupingSize2;
- }
- int maxDig = 0, minDig = 0, maxSigDig = 0;
- if (useSigDig) {
- minDig = getMinimumSignificantDigits();
- maxDig = maxSigDig = getMaximumSignificantDigits();
- } else {
- minDig = getMinimumIntegerDigits();
- maxDig = getMaximumIntegerDigits();
- }
- if (useExponentialNotation) {
- if (maxDig > MAX_SCIENTIFIC_INTEGER_DIGITS) {
- maxDig = 1;
- }
- } else if (useSigDig) {
- maxDig = Math.max(maxDig, g + 1);
- } else {
- maxDig = Math.max(Math.max(g, getMinimumIntegerDigits()), roundingDecimalPos) + 1;
- }
- for (i = maxDig; i > 0; --i) {
- if (!useExponentialNotation && i < maxDig && isGroupingPosition(i)) {
- result.append(group);
- }
- if (useSigDig) {
- // #@,@### (maxSigDig == 5, minSigDig == 2) 65 4321 (1-based pos,
- // count from the right) Use # if pos > maxSigDig or 1 <= pos <=
- // (maxSigDig - minSigDig) Use @ if (maxSigDig - minSigDig) < pos <=
- // maxSigDig
- result.append((maxSigDig >= i && i > (maxSigDig - minDig)) ? sigDigit : digit);
- } else {
- if (roundingDigits != null) {
- int pos = roundingDecimalPos - i;
- if (pos >= 0 && pos < roundingDigits.length()) {
- result.append((char) (roundingDigits.charAt(pos) - '0' + zero));
- continue;
- }
- }
- result.append(i <= minDig ? zero : digit);
- }
- }
- if (!useSigDig) {
- if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) {
- result.append(localized ? symbols.getDecimalSeparator() :
- PATTERN_DECIMAL_SEPARATOR);
- }
- int pos = roundingDecimalPos;
- for (i = 0; i < getMaximumFractionDigits(); ++i) {
- if (roundingDigits != null && pos < roundingDigits.length()) {
- result.append(pos < 0 ? zero :
- (char) (roundingDigits.charAt(pos) - '0' + zero));
- ++pos;
- continue;
- }
- result.append(i < getMinimumFractionDigits() ? zero : digit);
- }
- }
- if (useExponentialNotation) {
- if (localized) {
- result.append(symbols.getExponentSeparator());
- } else {
- result.append(PATTERN_EXPONENT);
- }
- if (exponentSignAlwaysShown) {
- result.append(localized ? symbols.getPlusSign() : PATTERN_PLUS_SIGN);
- }
- for (i = 0; i < minExponentDigits; ++i) {
- result.append(zero);
- }
- }
- if (padSpec != null && !useExponentialNotation) {
- int add = formatWidth
- - result.length()
- + sub0Start
- - ((part == 0)
- ? positivePrefix.length() + positiveSuffix.length()
- : negativePrefix.length() + negativeSuffix.length());
- while (add > 0) {
- result.insert(sub0Start, digit);
- ++maxDig;
- --add;
- // Only add a grouping separator if we have at least 2 additional
- // characters to be added, so we don't end up with ",###".
- if (add > 1 && isGroupingPosition(maxDig)) {
- result.insert(sub0Start, group);
- --add;
- }
- }
- }
- if (padPos == PAD_BEFORE_SUFFIX) {
- result.append(padSpec);
- }
- // Use original symbols read from resources in pattern eg. use "\u00A4"
- // instead of "$" in Locale.US [Richard/GCL]
- appendAffixPattern(result, part != 0, false, localized);
- if (padPos == PAD_AFTER_SUFFIX) {
- result.append(padSpec);
- }
- if (part == 0) {
- if (negativeSuffix.equals(positiveSuffix) &&
- negativePrefix.equals(PATTERN_MINUS_SIGN + positivePrefix)) {
- break;
- } else {
- result.append(localized ? symbols.getPatternSeparator() : PATTERN_SEPARATOR);
- }
- }
- }
- return result.toString();
- }
-
- /**
- * Applies the given pattern to this Format object. A pattern is a short-hand
- * specification for the various formatting properties. These properties can also be
- * changed individually through the various setter methods.
- *
- * There is no limit to integer digits are set by this routine, since that is the
- * typical end-user desire; use setMaximumInteger if you want to set a real value. For
- * negative numbers, use a second pattern, separated by a semicolon
- *
- * Example "#,#00.0#" -> 1,234.56
- *
- * This means a minimum of 2 integer digits, 1 fraction digit, and a maximum of 2
- * fraction digits.
- *
- * Example: "#,#00.0#;(#,#00.0#)" for negatives in parentheses.
- *
- * In negative patterns, the minimum and maximum counts are ignored; these are
- * presumed to be set in the positive pattern.
- *
- * @stable ICU 2.0
- */
- public void applyPattern(String pattern) {
- applyPattern(pattern, false);
- }
-
- /**
- * Applies the given pattern to this Format object. The pattern is assumed to be in a
- * localized notation. A pattern is a short-hand specification for the various
- * formatting properties. These properties can also be changed individually through
- * the various setter methods.
- *
- * There is no limit to integer digits are set by this routine, since that is the
- * typical end-user desire; use setMaximumInteger if you want to set a real value. For
- * negative numbers, use a second pattern, separated by a semicolon
- *
- * Example "#,#00.0#" -> 1,234.56
- *
- * This means a minimum of 2 integer digits, 1 fraction digit, and a maximum of 2
- * fraction digits.
- *
- * Example: "#,#00.0#;(#,#00.0#)" for negatives in parantheses.
- *
- * In negative patterns, the minimum and maximum counts are ignored; these are
- * presumed to be set in the positive pattern.
- *
- * @stable ICU 2.0
- */
- public void applyLocalizedPattern(String pattern) {
- applyPattern(pattern, true);
- }
-
- /**
- * Does the real work of applying a pattern.
- */
- private void applyPattern(String pattern, boolean localized) {
- applyPatternWithoutExpandAffix(pattern, localized);
- expandAffixAdjustWidth(null);
- }
-
- private void expandAffixAdjustWidth(String pluralCount) {
- // Bug 4212072 Update the affix strings according to symbols in order to keep the
- // affix strings up to date. [Richard/GCL]
- expandAffixes(pluralCount);
-
- // Now that we have the actual prefix and suffix, fix up formatWidth
- if (formatWidth > 0) {
- formatWidth += positivePrefix.length() + positiveSuffix.length();
- }
- }
-
- private void applyPatternWithoutExpandAffix(String pattern, boolean localized) {
- char zeroDigit = PATTERN_ZERO_DIGIT; // '0'
- char sigDigit = PATTERN_SIGNIFICANT_DIGIT; // '@'
- char groupingSeparator = PATTERN_GROUPING_SEPARATOR;
- char decimalSeparator = PATTERN_DECIMAL_SEPARATOR;
- char percent = PATTERN_PERCENT;
- char perMill = PATTERN_PER_MILLE;
- char digit = PATTERN_DIGIT; // '#'
- char separator = PATTERN_SEPARATOR;
- String exponent = String.valueOf(PATTERN_EXPONENT);
- char plus = PATTERN_PLUS_SIGN;
- char padEscape = PATTERN_PAD_ESCAPE;
- char minus = PATTERN_MINUS_SIGN; // Bug 4212072 [Richard/GCL]
- if (localized) {
- zeroDigit = symbols.getZeroDigit();
- sigDigit = symbols.getSignificantDigit();
- groupingSeparator = symbols.getGroupingSeparator();
- decimalSeparator = symbols.getDecimalSeparator();
- percent = symbols.getPercent();
- perMill = symbols.getPerMill();
- digit = symbols.getDigit();
- separator = symbols.getPatternSeparator();
- exponent = symbols.getExponentSeparator();
- plus = symbols.getPlusSign();
- padEscape = symbols.getPadEscape();
- minus = symbols.getMinusSign(); // Bug 4212072 [Richard/GCL]
- }
- char nineDigit = (char) (zeroDigit + 9);
-
- boolean gotNegative = false;
-
- int pos = 0;
- // Part 0 is the positive pattern. Part 1, if present, is the negative
- // pattern.
- for (int part = 0; part < 2 && pos < pattern.length(); ++part) {
- // The subpart ranges from 0 to 4: 0=pattern proper, 1=prefix, 2=suffix,
- // 3=prefix in quote, 4=suffix in quote. Subpart 0 is between the prefix and
- // suffix, and consists of pattern characters. In the prefix and suffix,
- // percent, permille, and currency symbols are recognized and translated.
- int subpart = 1, sub0Start = 0, sub0Limit = 0, sub2Limit = 0;
-
- // It's important that we don't change any fields of this object
- // prematurely. We set the following variables for the multiplier, grouping,
- // etc., and then only change the actual object fields if everything parses
- // correctly. This also lets us register the data from part 0 and ignore the
- // part 1, except for the prefix and suffix.
- StringBuilder prefix = new StringBuilder();
- StringBuilder suffix = new StringBuilder();
- int decimalPos = -1;
- int multpl = 1;
- int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0, sigDigitCount = 0;
- byte groupingCount = -1;
- byte groupingCount2 = -1;
- int padPos = -1;
- char padChar = 0;
- int incrementPos = -1;
- long incrementVal = 0;
- byte expDigits = -1;
- boolean expSignAlways = false;
- int currencySignCnt = 0;
-
- // The affix is either the prefix or the suffix.
- StringBuilder affix = prefix;
-
- int start = pos;
-
- PARTLOOP: for (; pos < pattern.length(); ++pos) {
- char ch = pattern.charAt(pos);
- switch (subpart) {
- case 0: // Pattern proper subpart (between prefix & suffix)
- // Process the digits, decimal, and grouping characters. We record
- // five pieces of information. We expect the digits to occur in the
- // pattern ####00.00####, and we record the number of left digits,
- // zero (central) digits, and right digits. The position of the last
- // grouping character is recorded (should be somewhere within the
- // first two blocks of characters), as is the position of the decimal
- // point, if any (should be in the zero digits). If there is no
- // decimal point, then there should be no right digits.
- if (ch == digit) {
- if (zeroDigitCount > 0 || sigDigitCount > 0) {
- ++digitRightCount;
- } else {
- ++digitLeftCount;
- }
- if (groupingCount >= 0 && decimalPos < 0) {
- ++groupingCount;
- }
- } else if ((ch >= zeroDigit && ch <= nineDigit) || ch == sigDigit) {
- if (digitRightCount > 0) {
- patternError("Unexpected '" + ch + '\'', pattern);
- }
- if (ch == sigDigit) {
- ++sigDigitCount;
- } else {
- ++zeroDigitCount;
- if (ch != zeroDigit) {
- int p = digitLeftCount + zeroDigitCount + digitRightCount;
- if (incrementPos >= 0) {
- while (incrementPos < p) {
- incrementVal *= 10;
- ++incrementPos;
- }
- } else {
- incrementPos = p;
- }
- incrementVal += ch - zeroDigit;
- }
- }
- if (groupingCount >= 0 && decimalPos < 0) {
- ++groupingCount;
- }
- } else if (ch == groupingSeparator) {
- // Bug 4212072 process the Localized pattern like
- // "'Fr. '#'##0.05;'Fr.-'#'##0.05" (Locale="CH", groupingSeparator
- // == QUOTE) [Richard/GCL]
- if (ch == QUOTE && (pos + 1) < pattern.length()) {
- char after = pattern.charAt(pos + 1);
- if (!(after == digit || (after >= zeroDigit && after <= nineDigit))) {
- // A quote outside quotes indicates either the opening
- // quote or two quotes, which is a quote literal. That is,
- // we have the first quote in 'do' or o''clock.
- if (after == QUOTE) {
- ++pos;
- // Fall through to append(ch)
- } else {
- if (groupingCount < 0) {
- subpart = 3; // quoted prefix subpart
- } else {
- // Transition to suffix subpart
- subpart = 2; // suffix subpart
- affix = suffix;
- sub0Limit = pos--;
- }
- continue;
- }
- }
- }
-
- if (decimalPos >= 0) {
- patternError("Grouping separator after decimal", pattern);
- }
- groupingCount2 = groupingCount;
- groupingCount = 0;
- } else if (ch == decimalSeparator) {
- if (decimalPos >= 0) {
- patternError("Multiple decimal separators", pattern);
- }
- // Intentionally incorporate the digitRightCount, even though it
- // is illegal for this to be > 0 at this point. We check pattern
- // syntax below.
- decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
- } else {
- if (pattern.regionMatches(pos, exponent, 0, exponent.length())) {
- if (expDigits >= 0) {
- patternError("Multiple exponential symbols", pattern);
- }
- if (groupingCount >= 0) {
- patternError("Grouping separator in exponential", pattern);
- }
- pos += exponent.length();
- // Check for positive prefix
- if (pos < pattern.length() && pattern.charAt(pos) == plus) {
- expSignAlways = true;
- ++pos;
- }
- // Use lookahead to parse out the exponential part of the
- // pattern, then jump into suffix subpart.
- expDigits = 0;
- while (pos < pattern.length() && pattern.charAt(pos) == zeroDigit) {
- ++expDigits;
- ++pos;
- }
-
- // 1. Require at least one mantissa pattern digit
- // 2. Disallow "#+ @" in mantissa
- // 3. Require at least one exponent pattern digit
- if (((digitLeftCount + zeroDigitCount) < 1 &&
- (sigDigitCount + digitRightCount) < 1)
- || (sigDigitCount > 0 && digitLeftCount > 0) || expDigits < 1) {
- patternError("Malformed exponential", pattern);
- }
- }
- // Transition to suffix subpart
- subpart = 2; // suffix subpart
- affix = suffix;
- sub0Limit = pos--; // backup: for() will increment
- continue;
- }
- break;
- case 1: // Prefix subpart
- case 2: // Suffix subpart
- // Process the prefix / suffix characters Process unquoted characters
- // seen in prefix or suffix subpart.
-
- // Several syntax characters implicitly begins the next subpart if we
- // are in the prefix; otherwise they are illegal if unquoted.
- if (ch == digit || ch == groupingSeparator || ch == decimalSeparator
- || (ch >= zeroDigit && ch <= nineDigit) || ch == sigDigit) {
- // Any of these characters implicitly begins the
- // next subpart if we are in the prefix
- if (subpart == 1) { // prefix subpart
- subpart = 0; // pattern proper subpart
- sub0Start = pos--; // Reprocess this character
- continue;
- } else if (ch == QUOTE) {
- // Bug 4212072 process the Localized pattern like
- // "'Fr. '#'##0.05;'Fr.-'#'##0.05" (Locale="CH",
- // groupingSeparator == QUOTE) [Richard/GCL]
-
- // A quote outside quotes indicates either the opening quote
- // or two quotes, which is a quote literal. That is, we have
- // the first quote in 'do' or o''clock.
- if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
- ++pos;
- affix.append(ch);
- } else {
- subpart += 2; // open quote
- }
- continue;
- }
- patternError("Unquoted special character '" + ch + '\'', pattern);
- } else if (ch == CURRENCY_SIGN) {
- // Use lookahead to determine if the currency sign is
- // doubled or not.
- boolean doubled = (pos + 1) < pattern.length() &&
- pattern.charAt(pos + 1) == CURRENCY_SIGN;
-
- // Bug 4212072 To meet the need of expandAffix(String,
- // StirngBuffer) [Richard/GCL]
- if (doubled) {
- ++pos; // Skip over the doubled character
- affix.append(ch); // append two: one here, one below
- if ((pos + 1) < pattern.length() &&
- pattern.charAt(pos + 1) == CURRENCY_SIGN) {
- ++pos; // Skip over the tripled character
- affix.append(ch); // append again
- currencySignCnt = CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT;
- } else {
- currencySignCnt = CURRENCY_SIGN_COUNT_IN_ISO_FORMAT;
- }
- } else {
- currencySignCnt = CURRENCY_SIGN_COUNT_IN_SYMBOL_FORMAT;
- }
- // Fall through to append(ch)
- } else if (ch == QUOTE) {
- // A quote outside quotes indicates either the opening quote or
- // two quotes, which is a quote literal. That is, we have the
- // first quote in 'do' or o''clock.
- if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
- ++pos;
- affix.append(ch); // append two: one here, one below
- } else {
- subpart += 2; // open quote
- }
- // Fall through to append(ch)
- } else if (ch == separator) {
- // Don't allow separators in the prefix, and don't allow
- // separators in the second pattern (part == 1).
- if (subpart == 1 || part == 1) {
- patternError("Unquoted special character '" + ch + '\'', pattern);
- }
- sub2Limit = pos++;
- break PARTLOOP; // Go to next part
- } else if (ch == percent || ch == perMill) {
- // Next handle characters which are appended directly.
- if (multpl != 1) {
- patternError("Too many percent/permille characters", pattern);
- }
- multpl = (ch == percent) ? 100 : 1000;
- // Convert to non-localized pattern
- ch = (ch == percent) ? PATTERN_PERCENT : PATTERN_PER_MILLE;
- // Fall through to append(ch)
- } else if (ch == minus) {
- // Convert to non-localized pattern
- ch = PATTERN_MINUS_SIGN;
- // Fall through to append(ch)
- } else if (ch == padEscape) {
- if (padPos >= 0) {
- patternError("Multiple pad specifiers", pattern);
- }
- if ((pos + 1) == pattern.length()) {
- patternError("Invalid pad specifier", pattern);
- }
- padPos = pos++; // Advance past pad char
- padChar = pattern.charAt(pos);
- continue;
- }
- affix.append(ch);
- break;
- case 3: // Prefix subpart, in quote
- case 4: // Suffix subpart, in quote
- // A quote within quotes indicates either the closing quote or two
- // quotes, which is a quote literal. That is, we have the second quote
- // in 'do' or 'don''t'.
- if (ch == QUOTE) {
- if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
- ++pos;
- affix.append(ch);
- } else {
- subpart -= 2; // close quote
- }
- // Fall through to append(ch)
- }
- // NOTE: In ICU 2.2 there was code here to parse quoted percent and
- // permille characters _within quotes_ and give them special
- // meaning. This is incorrect, since quoted characters are literals
- // without special meaning.
- affix.append(ch);
- break;
- }
- }
-
- if (subpart == 3 || subpart == 4) {
- patternError("Unterminated quote", pattern);
- }
-
- if (sub0Limit == 0) {
- sub0Limit = pattern.length();
- }
-
- if (sub2Limit == 0) {
- sub2Limit = pattern.length();
- }
-
- // Handle patterns with no '0' pattern character. These patterns are legal,
- // but must be recodified to make sense. "##.###" -> "#0.###". ".###" ->
- // ".0##".
- //
- // We allow patterns of the form "####" to produce a zeroDigitCount of zero
- // (got that?); although this seems like it might make it possible for
- // format() to produce empty strings, format() checks for this condition and
- // outputs a zero digit in this situation. Having a zeroDigitCount of zero
- // yields a minimum integer digits of zero, which allows proper round-trip
- // patterns. We don't want "#" to become "#0" when toPattern() is called (even
- // though that's what it really is, semantically).
- if (zeroDigitCount == 0 && sigDigitCount == 0 &&
- digitLeftCount > 0 && decimalPos >= 0) {
- // Handle "###.###" and "###." and ".###"
- int n = decimalPos;
- if (n == 0)
- ++n; // Handle ".###"
- digitRightCount = digitLeftCount - n;
- digitLeftCount = n - 1;
- zeroDigitCount = 1;
- }
-
- // Do syntax checking on the digits, decimal points, and quotes.
- if ((decimalPos < 0 && digitRightCount > 0 && sigDigitCount == 0)
- || (decimalPos >= 0
- && (sigDigitCount > 0
- || decimalPos < digitLeftCount
- || decimalPos > (digitLeftCount + zeroDigitCount)))
- || groupingCount == 0
- || groupingCount2 == 0
- || (sigDigitCount > 0 && zeroDigitCount > 0)
- || subpart > 2) { // subpart > 2 == unmatched quote
- patternError("Malformed pattern", pattern);
- }
-
- // Make sure pad is at legal position before or after affix.
- if (padPos >= 0) {
- if (padPos == start) {
- padPos = PAD_BEFORE_PREFIX;
- } else if (padPos + 2 == sub0Start) {
- padPos = PAD_AFTER_PREFIX;
- } else if (padPos == sub0Limit) {
- padPos = PAD_BEFORE_SUFFIX;
- } else if (padPos + 2 == sub2Limit) {
- padPos = PAD_AFTER_SUFFIX;
- } else {
- patternError("Illegal pad position", pattern);
- }
- }
-
- if (part == 0) {
- // Set negative affixes temporarily to match the positive
- // affixes. Fix this up later after processing both parts.
-
- // Bug 4212072 To meet the need of expandAffix(String, StirngBuffer)
- // [Richard/GCL]
- posPrefixPattern = negPrefixPattern = prefix.toString();
- posSuffixPattern = negSuffixPattern = suffix.toString();
-
- useExponentialNotation = (expDigits >= 0);
- if (useExponentialNotation) {
- minExponentDigits = expDigits;
- exponentSignAlwaysShown = expSignAlways;
- }
- int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount;
- // The effectiveDecimalPos is the position the decimal is at or would be
- // at if there is no decimal. Note that if decimalPos<0, then
- // digitTotalCount == digitLeftCount + zeroDigitCount.
- int effectiveDecimalPos = decimalPos >= 0 ? decimalPos : digitTotalCount;
- boolean useSigDig = (sigDigitCount > 0);
- setSignificantDigitsUsed(useSigDig);
- if (useSigDig) {
- setMinimumSignificantDigits(sigDigitCount);
- setMaximumSignificantDigits(sigDigitCount + digitRightCount);
- } else {
- int minInt = effectiveDecimalPos - digitLeftCount;
- setMinimumIntegerDigits(minInt);
-
- // Upper limit on integer and fraction digits for a Java double
- // [Richard/GCL]
- setMaximumIntegerDigits(useExponentialNotation ? digitLeftCount + minInt :
- DOUBLE_INTEGER_DIGITS);
- _setMaximumFractionDigits(decimalPos >= 0 ?
- (digitTotalCount - decimalPos) : 0);
- setMinimumFractionDigits(decimalPos >= 0 ?
- (digitLeftCount + zeroDigitCount - decimalPos) : 0);
- }
- setGroupingUsed(groupingCount > 0);
- this.groupingSize = (groupingCount > 0) ? groupingCount : 0;
- this.groupingSize2 = (groupingCount2 > 0 && groupingCount2 != groupingCount)
- ? groupingCount2 : 0;
- this.multiplier = multpl;
- setDecimalSeparatorAlwaysShown(decimalPos == 0 || decimalPos == digitTotalCount);
- if (padPos >= 0) {
- padPosition = padPos;
- formatWidth = sub0Limit - sub0Start; // to be fixed up below
- pad = padChar;
- } else {
- formatWidth = 0;
- }
- if (incrementVal != 0) {
- // BigDecimal scale cannot be negative (even though this makes perfect
- // sense), so we need to handle this.
- int scale = incrementPos - effectiveDecimalPos;
- roundingIncrementICU = BigDecimal.valueOf(incrementVal, scale > 0 ? scale : 0);
- if (scale < 0) {
- roundingIncrementICU = roundingIncrementICU.movePointRight(-scale);
- }
- roundingMode = BigDecimal.ROUND_HALF_EVEN;
- } else {
- setRoundingIncrement((BigDecimal) null);
- }
-
- // Update currency sign count for the new pattern
- currencySignCount = currencySignCnt;
- } else {
- // Bug 4212072 To meet the need of expandAffix(String, StirngBuffer)
- // [Richard/GCL]
- negPrefixPattern = prefix.toString();
- negSuffixPattern = suffix.toString();
- gotNegative = true;
- }
- }
-
-
- // Bug 4140009 Process the empty pattern [Richard/GCL]
- if (pattern.length() == 0) {
- posPrefixPattern = posSuffixPattern = "";
- setMinimumIntegerDigits(0);
- setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
- setMinimumFractionDigits(0);
- _setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
- }
-
- // If there was no negative pattern, or if the negative pattern is identical to
- // the positive pattern, then prepend the minus sign to the positive pattern to
- // form the negative pattern.
-
- // Bug 4212072 To meet the need of expandAffix(String, StirngBuffer) [Richard/GCL]
-
- if (!gotNegative ||
- (negPrefixPattern.equals(posPrefixPattern)
- && negSuffixPattern.equals(posSuffixPattern))) {
- negSuffixPattern = posSuffixPattern;
- negPrefixPattern = PATTERN_MINUS_SIGN + posPrefixPattern;
- }
- setLocale(null, null);
- // save the pattern
- formatPattern = pattern;
-
- // special handlings for currency instance
- if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
- // reset rounding increment and max/min fractional digits
- // by the currency
- Currency theCurrency = getCurrency();
- if (theCurrency != null) {
- setRoundingIncrement(theCurrency.getRoundingIncrement(currencyUsage));
- int d = theCurrency.getDefaultFractionDigits(currencyUsage);
- setMinimumFractionDigits(d);
- _setMaximumFractionDigits(d);
- }
-
- // initialize currencyPluralInfo if needed
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT
- && currencyPluralInfo == null) {
- currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
- }
- }
- resetActualRounding();
- }
-
-
- private void patternError(String msg, String pattern) {
- throw new IllegalArgumentException(msg + " in pattern \"" + pattern + '"');
- }
-
-
- // Rewrite the following 4 "set" methods Upper limit on integer and fraction digits
- // for a Java double [Richard/GCL]
-
- /**
- * Sets the maximum number of digits allowed in the integer portion of a number. This
- * override limits the integer digit count to 309.
- *
- * @see NumberFormat#setMaximumIntegerDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMaximumIntegerDigits(int newValue) {
- super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
- }
-
- /**
- * Sets the minimum number of digits allowed in the integer portion of a number. This
- * override limits the integer digit count to 309.
- *
- * @see NumberFormat#setMinimumIntegerDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMinimumIntegerDigits(int newValue) {
- super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
- }
-
- /**
- * {@icu} Returns the minimum number of significant digits that will be
- * displayed. This value has no effect unless {@link #areSignificantDigitsUsed()}
- * returns true.
- *
- * @return the fewest significant digits that will be shown
- * @stable ICU 3.0
- */
- public int getMinimumSignificantDigits() {
- return minSignificantDigits;
- }
-
- /**
- * {@icu} Returns the maximum number of significant digits that will be
- * displayed. This value has no effect unless {@link #areSignificantDigitsUsed()}
- * returns true.
- *
- * @return the most significant digits that will be shown
- * @stable ICU 3.0
- */
- public int getMaximumSignificantDigits() {
- return maxSignificantDigits;
- }
-
- /**
- * {@icu} Sets the minimum number of significant digits that will be displayed. If
- * This pattern is expanded by the method Note that the JDK 1.2 public API provides no way to set this
- * field, even though it is supported by the implementation and
- * the stream format. The intent is that this will be added to the
- * API in the future.
- *
- * @serial
- */
- private boolean useExponentialNotation; // Newly persistent in JDK 1.2
-
- /**
- * The minimum number of digits used to display the exponent when a number is
- * formatted in exponential notation. This field is ignored if
- * Note that the JDK 1.2 public API provides no way to set this field, even though
- * it is supported by the implementation and the stream format. The intent is that
- * this will be added to the API in the future.
- *
- * @serial
- */
- private byte minExponentDigits; // Newly persistent in JDK 1.2
-
- /**
- * If true, the exponent is always prefixed with either the plus sign or the minus
- * sign. Otherwise, only negative exponents are prefixed with the minus sign. This has
- * no effect unless Number
object to represent the
- * parsed value. Double
objects are returned to represent non-integral
- * values which cannot be stored in a BigDecimal
. These are
- * NaN
, infinity, -infinity, and -0.0. If {@link #isParseBigDecimal()} is
- * false (the default), all other values are returned as Long
,
- * BigInteger
, or BigDecimal
values, in that order of
- * preference. If {@link #isParseBigDecimal()} is true, all other values are returned
- * as BigDecimal
valuse. If the parse fails, null is returned.
- *
- * @param text the string to be parsed
- * @param parsePosition defines the position where parsing is to begin, and upon
- * return, the position where parsing left off. If the position has not changed upon
- * return, then parsing failed.
- * @return a Number
object with the parsed value or
- * null
if the parse failed
- * @stable ICU 2.0
- */
- @Override
- public Number parse(String text, ParsePosition parsePosition) {
- return (Number) parse(text, parsePosition, null);
- }
+ /**
+ * Returns a copy of the decimal format symbols used by this formatter.
+ *
+ * @return desired DecimalFormatSymbols
+ * @see DecimalFormatSymbols
+ * @stable ICU 2.0
+ */
+ public synchronized DecimalFormatSymbols getDecimalFormatSymbols() {
+ return (DecimalFormatSymbols) symbols.clone();
+ }
- /**
- * Parses text from the given string as a CurrencyAmount. Unlike the parse() method,
- * this method will attempt to parse a generic currency name, searching for a match of
- * this object's locale's currency display names, or for a 3-letter ISO currency
- * code. This method will fail if this format is not a currency format, that is, if it
- * does not contain the currency pattern symbol (U+00A4) in its prefix or suffix.
- *
- * @param text the text to parse
- * @param pos input-output position; on input, the position within text to match; must
- * have 0 <= pos.getIndex() < text.length(); on output, the position after the last
- * matched character. If the parse fails, the position in unchanged upon output.
- * @return a CurrencyAmount, or null upon failure
- * @stable ICU 49
- */
- @Override
- public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
- Currency[] currency = new Currency[1];
- return (CurrencyAmount) parse(text.toString(), pos, currency);
- }
+ /**
+ * Sets the decimal format symbols used by this formatter. The formatter uses a copy of the
+ * provided symbols.
+ *
+ * @param newSymbols desired DecimalFormatSymbols
+ * @see DecimalFormatSymbols
+ * @stable ICU 2.0
+ */
+ public synchronized void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) {
+ symbols = (DecimalFormatSymbols) newSymbols.clone();
+ refreshFormatter();
+ }
- /**
- * Parses the given text as either a Number or a CurrencyAmount.
- *
- * @param text the string to parse
- * @param parsePosition input-output position; on input, the position within text to
- * match; must have 0 <= pos.getIndex() < text.length(); on output, the position after
- * the last matched character. If the parse fails, the position in unchanged upon
- * output.
- * @param currency if non-null, a CurrencyAmount is parsed and returned; otherwise a
- * Number is parsed and returned
- * @return a Number or CurrencyAmount or null
- */
- private Object parse(String text, ParsePosition parsePosition, Currency[] currency) {
- int backup;
- int i = backup = parsePosition.getIndex();
+ /**
+ * Affixes: Gets the positive prefix string currently being used to format
+ * numbers.
+ *
+ * decVal
and
- * returns matched length.
- *
- * @param str The input string
- * @param start The start index
- * @param decVal Receives decimal value
- * @return Length of match, or 0 if the sequence at the position is not
- * a decimal digit.
- */
- private int matchesDigit(String str, int start, int[] decVal) {
- String[] localeDigits = symbols.getDigitStringsLocal();
+ /**
+ * @return The increment to which numbers are being rounded.
+ * @see #setRoundingIncrement
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ public synchronized java.math.BigDecimal getRoundingIncrement() {
+ return exportedProperties.getRoundingIncrement();
+ }
- // Check if the sequence at the current position matches locale digits.
- for (int i = 0; i < 10; i++) {
- int digitStrLen = localeDigits[i].length();
- if (str.regionMatches(start, localeDigits[i], 0, digitStrLen)) {
- decVal[0] = i;
- return digitStrLen;
- }
- }
-
- // If no locale digit match, then check if this is a Unicode digit
- int cp = str.codePointAt(start);
- decVal[0] = UCharacter.digit(cp, 10);
- if (decVal[0] >= 0) {
- return Character.charCount(cp);
- }
-
- return 0;
+ /**
+ * Rounding and Digit Limits: Sets an increment, or interval, to which numbers
+ * are rounded. For example, a rounding increment of 0.05 will cause the number 1.23 to be rounded
+ * to 1.25 in the default rounding mode.
+ *
+ *
+ * df.setRoundingMode(BigDecimal.ROUND_CEILING);
+ * df.setRoundingMode(RoundingMode.CEILING.ordinal());
+ *
+ *
+ * @param roundingMode The integer constant rounding mode to use when formatting numbers.
+ * @category Rounding
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setRoundingMode(int roundingMode) {
+ properties.setRoundingMode(RoundingMode.valueOf(roundingMode));
+ refreshFormatter();
+ }
- StringBuilder buf = new StringBuilder();
- buf.append(affix, 0, idx);
- idx++; // skip the first Bidi mark
- for (; idx < affix.length(); idx++) {
- char c = affix.charAt(idx);
- if (!isBidiMark(c)) {
- buf.append(c);
- }
- }
+ /**
+ * @return The {@link java.math.MathContext} being used to round numbers.
+ * @see #setMathContext
+ * @category Rounding
+ * @stable ICU 4.2
+ */
+ public synchronized java.math.MathContext getMathContext() {
+ java.math.MathContext mathContext = exportedProperties.getMathContext();
+ assert mathContext != null;
+ return mathContext;
+ }
- return buf.toString();
- }
+ /**
+ * Rounding and Digit Limits: Sets the {@link java.math.MathContext} used to
+ * round numbers. A "math context" encodes both a rounding mode and a number of significant
+ * digits.
+ *
+ * df.setSignificantDigitsUsed(true)
is functionally equivalent to:
+ *
+ *
+ * df.setMinimumSignificantDigits(1);
+ * df.setMaximumSignificantDigits(6);
+ *
+ *
+ * @param useSignificantDigits true to enable significant digit rounding; false to disable it.
+ * @category Rounding
+ * @stable ICU 3.0
+ */
+ public synchronized void setSignificantDigitsUsed(boolean useSignificantDigits) {
+ if (useSignificantDigits) {
+ // These are the default values from the old implementation.
+ properties.setMinimumSignificantDigits(1);
+ properties.setMaximumSignificantDigits(6);
+ } else {
+ properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGNIFICANT_DIGITS);
+ properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS);
+ properties.setSignificantDigitsMode(null);
}
+ refreshFormatter();
+ }
- /**
- * Matches a single character at text[pos] and return the index of the next character
- * upon success. Return -1 on failure. If ch is a Pattern_White_Space then match a run of
- * white space in text.
- */
- static final int match(String text, int pos, int ch) {
- if (pos < 0 || pos >= text.length()) {
- return -1;
- }
- pos = skipBidiMarks(text, pos);
- if (PatternProps.isWhiteSpace(ch)) {
- // Advance over run of white space in input text
- // Must see at least one white space char in input
- int s = pos;
- pos = skipPatternWhiteSpace(text, pos);
- if (pos == s) {
- return -1;
- }
- return pos;
- }
- if (pos >= text.length() || UTF16.charAt(text, pos) != ch) {
- return -1;
- }
- pos = skipBidiMarks(text, pos + UTF16.getCharCount(ch));
- return pos;
- }
+ /**
+ * @return The effective minimum number of significant digits displayed.
+ * @see #setMinimumSignificantDigits
+ * @category Rounding
+ * @stable ICU 3.0
+ */
+ public synchronized int getMinimumSignificantDigits() {
+ return exportedProperties.getMinimumSignificantDigits();
+ }
- /**
- * Matches a string at text[pos] and return the index of the next character upon
- * success. Return -1 on failure. Match a run of white space in str with a run of
- * white space in text.
- */
- static final int match(String text, int pos, String str) {
- for (int i = 0; i < str.length() && pos >= 0;) {
- int ch = UTF16.charAt(str, i);
- i += UTF16.getCharCount(ch);
- if (isBidiMark(ch)) {
- continue;
- }
- pos = match(text, pos, ch);
- if (PatternProps.isWhiteSpace(ch)) {
- i = skipPatternWhiteSpace(str, i);
- }
- }
- return pos;
- }
+ /**
+ * Rounding and Digit Limits: Sets the minimum number of significant digits to be
+ * displayed. If the number of significant digits is less than this value, the number will be
+ * padded with zeros as necessary.
+ *
+ *
+ *
+ *
+ *
+ * Mode A | Mode B | Mode C
+ * ---------+----------+----------
+ * 12340.0 | 12340.0 | 12340.0
+ * 1234.0 | 1234.0 | 1234.0
+ * 123.4 | 123.4 | 123.4
+ * 12.34 | 12.34 | 12.34
+ * 1.234 | 1.23 | 1.23
+ * 0.1234 | 0.12 | 0.123
+ * 0.01234 | 0.01 | 0.0123
+ * 0.001234 | 0.00 | 0.00123
+ *
+ *
+ * @param mode The significant digits mode to use.
+ * @category Rounding
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized void setSignificantDigitsMode(SignificantDigitsMode mode) {
+ properties.setSignificantDigitsMode(mode);
+ refreshFormatter();
+ }
- // With the introduction of the Currency object, the currency symbols in the DFS
- // object are ignored. For backward compatibility, we check any explicitly set DFS
- // object. If it is a default symbols object for its locale, we change the
- // currency object to one for that locale. If it is custom, we set the currency to
- // null.
- DecimalFormatSymbols def = new DecimalFormatSymbols(symbols.getULocale());
+ /**
+ * @return The minimum number of characters in formatted output.
+ * @see #setFormatWidth
+ * @category Padding
+ * @stable ICU 2.0
+ */
+ public synchronized int getFormatWidth() {
+ return exportedProperties.getFormatWidth();
+ }
- if (symbols.getCurrencySymbol().equals(def.getCurrencySymbol())
- && symbols.getInternationalCurrencySymbol()
- .equals(def.getInternationalCurrencySymbol())) {
- setCurrency(Currency.getInstance(symbols.getULocale()));
- } else {
- setCurrency(null);
- }
- }
+ /**
+ * Padding: Sets the minimum width of the string output by the formatting
+ * pipeline. For example, if padding is enabled and paddingWidth is set to 6, formatting the
+ * number "3.14159" with the pattern "0.00" will result in "··3.14" if '·' is your padding string.
+ *
+ *
+ *
+ *
+ * @param padPos The position used for padding.
+ * @see #setFormatWidth
+ * @category Padding
+ * @stable ICU 2.0
+ */
+ public synchronized void setPadPosition(int padPos) {
+ properties.setPadPosition(PadPosition.fromOld(padPos));
+ refreshFormatter();
+ }
- /**
- * Returns the positive suffix.
- *
- * df.setScientificNotation(true)
is functionally equivalent to calling
+ * df.setMinimumExponentDigits(1)
.
+ *
+ * @param useScientific true to enable scientific notation; false to disable it.
+ * @see #setMinimumExponentDigits
+ * @category ScientificNotation
+ * @stable ICU 2.0
+ */
+ public synchronized void setScientificNotation(boolean useScientific) {
+ if (useScientific) {
+ properties.setMinimumExponentDigits(1);
+ } else {
+ properties.setMinimumExponentDigits(Properties.DEFAULT_MINIMUM_EXPONENT_DIGITS);
}
+ refreshFormatter();
+ }
- /**
- * Returns the negative suffix.
- *
- * null
if a custom rounding
- * increment is not in effect.
- * @see #setRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 2.0
- */
- public java.math.BigDecimal getRoundingIncrement() {
- if (roundingIncrementICU == null)
- return null;
- return roundingIncrementICU.toBigDecimal();
- }
+ /**
+ * @return Whether or not grouping separators are to be printed in the output.
+ * @see #setGroupingUsed
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized boolean isGroupingUsed() {
+ return PositiveDecimalFormat.useGrouping(properties);
+ }
- /**
- * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
- * will be rounded to the number of digits displayed.
- *
- * @param newValue A positive rounding increment, or null
or
- * BigDecimal(0.0)
to use the default rounding increment.
- * @throws IllegalArgumentException if newValue
is < 0.0
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 2.0
- */
- public void setRoundingIncrement(java.math.BigDecimal newValue) {
- if (newValue == null) {
- setRoundingIncrement((BigDecimal) null);
- } else {
- setRoundingIncrement(new BigDecimal(newValue));
- }
+ /**
+ * Grouping: Sets whether grouping is to be used when formatting numbers.
+ * Grouping means whether the thousands, millions, billions, and larger powers of ten should be
+ * separated by a grouping separator (a comma in en-US).
+ *
+ * df.setGroupingUsed(true)
is functionally equivalent to setting grouping
+ * size to 3, as in df.setGroupingSize(3)
.
+ *
+ * @param enabled true to enable grouping separators; false to disable them.
+ * @see #setGroupingSize
+ * @see #setSecondaryGroupingSize
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized void setGroupingUsed(boolean enabled) {
+ if (enabled) {
+ // Set to a reasonable default value
+ properties.setGroupingSize(3);
+ } else {
+ properties.setGroupingSize(Properties.DEFAULT_GROUPING_SIZE);
+ properties.setSecondaryGroupingSize(Properties.DEFAULT_SECONDARY_GROUPING_SIZE);
}
+ refreshFormatter();
+ }
- /**
- * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
- * will be rounded to the number of digits displayed.
- *
- * @param newValue A positive rounding increment, or null
or
- * BigDecimal(0.0)
to use the default rounding increment.
- * @throws IllegalArgumentException if newValue
is < 0.0
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 3.6
- */
- public void setRoundingIncrement(BigDecimal newValue) {
- int i = newValue == null ? 0 : newValue.compareTo(BigDecimal.ZERO);
- if (i < 0) {
- throw new IllegalArgumentException("Illegal rounding increment");
- }
- if (i == 0) {
- setInternalRoundingIncrement(null);
- } else {
- setInternalRoundingIncrement(newValue);
- }
- resetActualRounding();
- }
+ /**
+ * @return The primary grouping size in use.
+ * @see #setGroupingSize
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ public synchronized int getGroupingSize() {
+ return exportedProperties.getGroupingSize();
+ }
- /**
- * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
- * will be rounded to the number of digits displayed.
- *
- * @param newValue A positive rounding increment, or 0.0 to use the default
- * rounding increment.
- * @throws IllegalArgumentException if newValue
is < 0.0
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 2.0
- */
- public void setRoundingIncrement(double newValue) {
- if (newValue < 0.0) {
- throw new IllegalArgumentException("Illegal rounding increment");
- }
- if (newValue == 0.0d) {
- setInternalRoundingIncrement((BigDecimal) null);
- } else {
- // Should use BigDecimal#valueOf(double) instead of constructor
- // to avoid the double precision problem.
- setInternalRoundingIncrement(BigDecimal.valueOf(newValue));
- }
- resetActualRounding();
- }
+ /**
+ * Grouping: Sets the primary grouping size (distance between grouping
+ * separators) used when formatting large numbers. For most locales, this defaults to 3: the
+ * number of digits between the ones and thousands place, between thousands and millions, and so
+ * forth.
+ *
+ * BigDecimal.ROUND_UP
and
- * BigDecimal.ROUND_UNNECESSARY
.
- * @see #setRoundingIncrement
- * @see #getRoundingIncrement
- * @see #setRoundingMode
- * @see java.math.BigDecimal
- * @stable ICU 2.0
- */
- @Override
- public int getRoundingMode() {
- return roundingMode;
- }
+ /**
+ * @return The secondary grouping size in use.
+ * @see #setSecondaryGroupingSize
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ public synchronized int getSecondaryGroupingSize() {
+ return exportedProperties.getSecondaryGroupingSize();
+ }
- /**
- * Sets the rounding mode. This has no effect unless the rounding increment is greater
- * than zero.
- *
- * @param roundingMode A rounding mode, between BigDecimal.ROUND_UP
and
- * BigDecimal.ROUND_UNNECESSARY
.
- * @exception IllegalArgumentException if roundingMode
is unrecognized.
- * @see #setRoundingIncrement
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see java.math.BigDecimal
- * @stable ICU 2.0
- */
- @Override
- public void setRoundingMode(int roundingMode) {
- if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) {
- throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode);
- }
+ /**
+ * Grouping: Sets the secondary grouping size (distance between grouping
+ * separators after the first separator) used when formatting large numbers. In many south Asian
+ * locales, this is set to 2.
+ *
+ * format()
is padded. The width is
- * counted in 16-bit code units.
- *
- * @return the format width, or zero if no padding is in effect
- * @see #setFormatWidth
- * @see #getPadCharacter
- * @see #setPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public int getFormatWidth() {
- return formatWidth;
- }
+ /**
+ * Sets the minimum number of digits that must be before the first grouping separator in order for
+ * the grouping separator to be printed. For example, if minimum grouping digits is set to 2, in
+ * en-US, 1234 will be printed as "1234" and 12345 will be printed as "12,345".
+ *
+ * @param number The minimum number of digits before grouping is triggered.
+ * @category Separators
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized void setMinimumGroupingDigits(int number) {
+ properties.setMinimumGroupingDigits(number);
+ refreshFormatter();
+ }
- /**
- * Sets the width to which the output of format()
is
- * padded. The width is counted in 16-bit code units. This method
- * also controls whether padding is enabled.
- *
- * @param width the width to which to pad the result of
- * format()
, or zero to disable padding
- * @exception IllegalArgumentException if width
is < 0
- * @see #getFormatWidth
- * @see #getPadCharacter
- * @see #setPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public void setFormatWidth(int width) {
- if (width < 0) {
- throw new IllegalArgumentException("Illegal format width");
- }
- formatWidth = width;
- }
+ /**
+ * @return Whether the decimal separator is shown on integers.
+ * @see #setDecimalSeparatorAlwaysShown
+ * @category Separators
+ * @stable ICU 2.0
+ */
+ public synchronized boolean isDecimalSeparatorAlwaysShown() {
+ return exportedProperties.getDecimalSeparatorAlwaysShown();
+ }
- /**
- * {@icu} Returns the character used to pad to the format width. The default is ' '.
- *
- * @return the pad character
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #setPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public char getPadCharacter() {
- return pad;
- }
+ /**
+ * Separators: Sets whether the decimal separator (a period in en-US) is
+ * shown on integers. For example, if this setting is turned on, formatting 123 will result in
+ * "123." with the decimal separator.
+ *
+ * format()
is shorter
- * than the format width.
- *
- * @return the pad position, one of PAD_BEFORE_PREFIX
,
- * PAD_AFTER_PREFIX
, PAD_BEFORE_SUFFIX
, or
- * PAD_AFTER_SUFFIX
.
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #setPadCharacter
- * @see #getPadCharacter
- * @see #setPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public int getPadPosition() {
- return padPosition;
+ /**
+ * Sets the currency to be used when formatting numbers. The effect is twofold:
+ *
+ *
+ *
+ *
+ * Important: Displaying the currency in the output requires that the patter
+ * associated with this formatter contains a currency symbol '¤'. This will be the case if the
+ * instance was created via {@link #getCurrencyInstance} or one of its friends.
+ *
+ * @param currency The currency to use.
+ * @category Currency
+ * @stable ICU 2.2
+ */
+ @Override
+ public synchronized void setCurrency(Currency currency) {
+ properties.setCurrency(currency);
+ // Backwards compatibility: also set the currency in the DecimalFormatSymbols
+ if (currency != null) {
+ symbols.setCurrency(currency);
+ String symbol = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
+ symbols.setCurrencySymbol(symbol);
}
+ refreshFormatter();
+ }
- /**
- * {@icu} Sets the position at which padding will take place. This is the location at
- * which padding will be inserted if the result of format()
is shorter
- * than the format width. This has no effect unless padding is enabled.
- *
- * @param padPos the pad position, one of PAD_BEFORE_PREFIX
,
- * PAD_AFTER_PREFIX
, PAD_BEFORE_SUFFIX
, or
- * PAD_AFTER_SUFFIX
.
- * @exception IllegalArgumentException if the pad position in unrecognized
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #setPadCharacter
- * @see #getPadCharacter
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public void setPadPosition(int padPos) {
- if (padPos < PAD_BEFORE_PREFIX || padPos > PAD_AFTER_SUFFIX) {
- throw new IllegalArgumentException("Illegal pad position");
- }
- padPosition = padPos;
+ /**
+ * @return The strategy for rounding currency amounts.
+ * @see #setCurrencyUsage
+ * @category Currency
+ * @stable ICU 54
+ */
+ public synchronized CurrencyUsage getCurrencyUsage() {
+ // CurrencyUsage is not exported, so we have to get it from the input property bag.
+ // TODO: Should we export CurrencyUsage instead?
+ CurrencyUsage usage = properties.getCurrencyUsage();
+ if (usage == null) {
+ usage = CurrencyUsage.STANDARD;
}
+ return usage;
+ }
- /**
- * {@icu} Returns whether or not scientific notation is used.
- *
- * @return true if this object formats and parses scientific notation
- * @see #setScientificNotation
- * @see #getMinimumExponentDigits
- * @see #setMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public boolean isScientificNotation() {
- return useExponentialNotation;
- }
+ /**
+ * Sets the currency-dependent strategy to use when rounding numbers. There are two strategies:
+ *
+ *
+ *
+ *
+ * CASH mode is relevant in currencies that do not have tender down to the penny. For more
+ * information on the two rounding strategies, see UTS
+ * #35. If omitted, the strategy defaults to STANDARD. To override currency rounding
+ * altogether, use {@link #setMinimumFractionDigits} and {@link #setMaximumFractionDigits} or
+ * {@link #setRoundingIncrement}.
+ *
+ * @param usage The strategy to use when rounding in the current currency.
+ * @category Currency
+ * @stable ICU 54
+ */
+ public synchronized void setCurrencyUsage(CurrencyUsage usage) {
+ properties.setCurrencyUsage(usage);
+ refreshFormatter();
+ }
- /**
- * {@icu} Sets whether or not scientific notation is used. When scientific notation is
- * used, the effective maximum number of integer digits is <= 8. If the maximum number
- * of integer digits is set to more than 8, the effective maximum will be 1. This
- * allows this call to generate a 'default' scientific number format without
- * additional changes.
- *
- * @param useScientific true if this object formats and parses scientific notation
- * @see #isScientificNotation
- * @see #getMinimumExponentDigits
- * @see #setMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public void setScientificNotation(boolean useScientific) {
- useExponentialNotation = useScientific;
- }
+ /**
+ * @return The current instance of CurrencyPluralInfo.
+ * @see #setCurrencyPluralInfo
+ * @category Currency
+ * @stable ICU 4.2
+ */
+ public CurrencyPluralInfo getCurrencyPluralInfo() {
+ // CurrencyPluralInfo also is not exported.
+ return properties.getCurrencyPluralInfo();
+ }
- /**
- * {@icu} Returns the minimum exponent digits that will be shown.
- *
- * @return the minimum exponent digits that will be shown
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #setMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public byte getMinimumExponentDigits() {
- return minExponentDigits;
- }
+ /**
+ * Sets a custom instance of CurrencyPluralInfo. CurrencyPluralInfo generates pattern strings for
+ * printing currency long names.
+ *
+ * NumberFormat.getInstance(NumberFormat.PLURALCURRENCYSTYLE)
.
+ *
+ * @param newInfo The CurrencyPluralInfo to use when printing currency long names.
+ * @category Currency
+ * @stable ICU 4.2
+ */
+ public void setCurrencyPluralInfo(CurrencyPluralInfo newInfo) {
+ properties.setCurrencyPluralInfo(newInfo);
+ refreshFormatter();
+ }
- /**
- * {@icu} Sets the minimum exponent digits that will be shown. This has no effect
- * unless scientific notation is in use.
- *
- * @param minExpDig a value >= 1 indicating the fewest exponent
- * digits that will be shown
- * @exception IllegalArgumentException if minExpDig
< 1
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #getMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public void setMinimumExponentDigits(byte minExpDig) {
- if (minExpDig < 1) {
- throw new IllegalArgumentException("Exponent digits must be >= 1");
- }
- minExponentDigits = minExpDig;
- }
+ /**
+ * @return Whether {@link #parse} will always return a BigDecimal
+ * @see #setParseBigDecimal
+ * @category Parsing
+ * @stable ICU 3.6
+ */
+ public synchronized boolean isParseBigDecimal() {
+ return properties.getParseToBigDecimal();
+ }
- /**
- * {@icu} Returns whether the exponent sign is always shown.
- *
- * @return true if the exponent is always prefixed with either the localized minus
- * sign or the localized plus sign, false if only negative exponents are prefixed with
- * the localized minus sign.
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #setMinimumExponentDigits
- * @see #getMinimumExponentDigits
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public boolean isExponentSignAlwaysShown() {
- return exponentSignAlwaysShown;
- }
+ /**
+ * Whether to force {@link #parse} to always return a BigDecimal. By default, {@link #parse} will
+ * return different data types as follows:
+ *
+ *
+ *
+ *
+ * If this setting is enabled, a BigDecimal will be returned even if the number is an integer.
+ *
+ * @param value true to cause {@link #parse} to always return a BigDecimal; false to let {@link
+ * #parse} return different data types.
+ * @category Parsing
+ * @stable ICU 3.6
+ */
+ public synchronized void setParseBigDecimal(boolean value) {
+ properties.setParseToBigDecimal(value);
+ // refreshFormatter() not needed
+ }
- /**
- * {@icu} Sets whether the exponent sign is always shown. This has no effect unless
- * scientific notation is in use.
- *
- * @param expSignAlways true if the exponent is always prefixed with either the
- * localized minus sign or the localized plus sign, false if only negative exponents
- * are prefixed with the localized minus sign.
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #setMinimumExponentDigits
- * @see #getMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public void setExponentSignAlwaysShown(boolean expSignAlways) {
- exponentSignAlwaysShown = expSignAlways;
- }
+ /**
+ * @return Always 1000, the default prior to ICU 59.
+ * @category Parsing
+ * @deprecated Setting max parse digits has no effect since ICU4J 59.
+ */
+ @Deprecated
+ public int getParseMaxDigits() {
+ return 1000;
+ }
- /**
- * Returns the grouping size. Grouping size is the number of digits between grouping
- * separators in the integer portion of a number. For example, in the number
- * "123,456.78", the grouping size is 3.
- *
- * @see #setGroupingSize
- * @see NumberFormat#isGroupingUsed
- * @see DecimalFormatSymbols#getGroupingSeparator
- * @stable ICU 2.0
- */
- public int getGroupingSize() {
- return groupingSize;
- }
+ /**
+ * @param maxDigits Prior to ICU 59, the maximum number of digits in the output number after
+ * exponential notation is applied.
+ * @category Parsing
+ * @deprecated Setting max parse digits has no effect since ICU4J 59.
+ */
+ @Deprecated
+ public void setParseMaxDigits(int maxDigits) {}
- /**
- * Sets the grouping size. Grouping size is the number of digits between grouping
- * separators in the integer portion of a number. For example, in the number
- * "123,456.78", the grouping size is 3.
- *
- * @see #getGroupingSize
- * @see NumberFormat#setGroupingUsed
- * @see DecimalFormatSymbols#setGroupingSeparator
- * @stable ICU 2.0
- */
- public void setGroupingSize(int newValue) {
- groupingSize = (byte) newValue;
- }
+ /**
+ * {@inheritDoc}
+ *
+ * @category Parsing
+ * @stable ICU 3.6
+ */
+ @Override
+ public synchronized boolean isParseStrict() {
+ return properties.getParseMode() == Parse.ParseMode.STRICT;
+ }
- /**
- * {@icu} Returns the secondary grouping size. In some locales one grouping interval
- * is used for the least significant integer digits (the primary grouping size), and
- * another is used for all others (the secondary grouping size). A formatter
- * supporting a secondary grouping size will return a positive integer unequal to the
- * primary grouping size returned by getGroupingSize()
. For example, if
- * the primary grouping size is 4, and the secondary grouping size is 2, then the
- * number 123456789 formats as "1,23,45,6789", and the pattern appears as "#,##,###0".
- *
- * @return the secondary grouping size, or a value less than one if there is none
- * @see #setSecondaryGroupingSize
- * @see NumberFormat#isGroupingUsed
- * @see DecimalFormatSymbols#getGroupingSeparator
- * @stable ICU 2.0
- */
- public int getSecondaryGroupingSize() {
- return groupingSize2;
- }
+ /**
+ * {@inheritDoc}
+ *
+ * @category Parsing
+ * @stable ICU 3.6
+ */
+ @Override
+ public synchronized void setParseStrict(boolean parseStrict) {
+ Parse.ParseMode mode = parseStrict ? Parse.ParseMode.STRICT : Parse.ParseMode.LENIENT;
+ properties.setParseMode(mode);
+ // refreshFormatter() not needed
+ }
- /**
- * {@icu} Sets the secondary grouping size. If set to a value less than 1, then
- * secondary grouping is turned off, and the primary grouping size is used for all
- * intervals, not just the least significant.
- *
- * @see #getSecondaryGroupingSize
- * @see NumberFormat#setGroupingUsed
- * @see DecimalFormatSymbols#setGroupingSeparator
- * @stable ICU 2.0
- */
- public void setSecondaryGroupingSize(int newValue) {
- groupingSize2 = (byte) newValue;
- }
+ /**
+ * @return Whether parsing should stop before encountering a decimal point and fraction part.
+ * @see #setParseIntegerOnly
+ * @category Parsing
+ * @stable ICU 2.0
+ */
+ @Override
+ public synchronized boolean isParseIntegerOnly() {
+ return properties.getParseIntegerOnly();
+ }
- /**
- * {@icu} Returns the MathContext used by this format.
- *
- * @return desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public MathContext getMathContextICU() {
- return mathContext;
- }
+ /**
+ * Parsing: Whether to ignore the fraction part of a number when parsing
+ * (defaults to false). If a string contains a decimal point, parsing will stop before the decimal
+ * point. Note that determining whether a character is a decimal point depends on the locale.
+ *
+ *
+ * // Require a decimal point in the string being parsed:
+ * df.applyPattern("#.");
+ * df.setDecimalPatternMatchRequired(true);
+ *
+ * // Alternatively:
+ * df.setDecimalSeparatorAlwaysShown(true);
+ * df.setDecimalPatternMatchRequired(true);
+ *
+ *
+ * To forbid a decimal point, call this method in combination with a pattern containing
+ * no decimal point. Alternatively, use {@link #setParseIntegerOnly} for the same behavior without
+ * depending on the contents of the pattern string.
+ *
+ *
+ * // Forbid a decimal point in the string being parsed:
+ * df.applyPattern("#");
+ * df.setDecimalPatternMatchRequired(true);
+ *
+ *
+ * @param value true to either require or forbid the decimal point according to the pattern; false
+ * to disable this feature.
+ * @see #setParseIntegerOnly
+ * @category Parsing
+ * @stable ICU 54
+ */
+ public synchronized void setDecimalPatternMatchRequired(boolean value) {
+ properties.setDecimalPatternMatchRequired(value);
+ refreshFormatter();
+ }
- /**
- * {@icu} Sets the MathContext used by this format.
- *
- * @param newValue desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public void setMathContext(java.math.MathContext newValue) {
- mathContext = new MathContext(newValue.getPrecision(), MathContext.SCIENTIFIC, false,
- (newValue.getRoundingMode()).ordinal());
- }
+ /**
+ * @return Whether to ignore exponents when parsing.
+ * @see #setParseNoExponent
+ * @category Parsing
+ * @internal
+ * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release.
+ */
+ @Deprecated
+ public synchronized boolean getParseNoExponent() {
+ return properties.getParseNoExponent();
+ }
- /**
- * Returns the behavior of the decimal separator with integers. (The decimal
- * separator will always appear with decimals.) min
is less than one then it is set to one. If the maximum significant
- * digits count is less than min
, then it is set to min
.
- * This function also enables the use of significant digits by this formatter -
- * {@link #areSignificantDigitsUsed()} will return true.
- *
- * @param min the fewest significant digits to be shown
- * @stable ICU 3.0
- */
- public void setMinimumSignificantDigits(int min) {
- if (min < 1) {
- min = 1;
- }
- // pin max sig dig to >= min
- int max = Math.max(maxSignificantDigits, min);
- minSignificantDigits = min;
- maxSignificantDigits = max;
- setSignificantDigitsUsed(true);
- }
-
- /**
- * {@icu} Sets the maximum number of significant digits that will be displayed. If
- * max
is less than one then it is set to one. If the minimum significant
- * digits count is greater than max
, then it is set to max
.
- * This function also enables the use of significant digits by this formatter -
- * {@link #areSignificantDigitsUsed()} will return true.
- *
- * @param max the most significant digits to be shown
- * @stable ICU 3.0
- */
- public void setMaximumSignificantDigits(int max) {
- if (max < 1) {
- max = 1;
- }
- // pin min sig dig to 1..max
- int min = Math.min(minSignificantDigits, max);
- minSignificantDigits = min;
- maxSignificantDigits = max;
- setSignificantDigitsUsed(true);
- }
-
- /**
- * {@icu} Returns true if significant digits are in use or false if integer and
- * fraction digit counts are in use.
- *
- * @return true if significant digits are in use
- * @stable ICU 3.0
- */
- public boolean areSignificantDigitsUsed() {
- return useSignificantDigits;
- }
-
- /**
- * {@icu} Sets whether significant digits are in use, or integer and fraction digit
- * counts are in use.
- *
- * @param useSignificantDigits true to use significant digits, or false to use integer
- * and fraction digit counts
- * @stable ICU 3.0
- */
- public void setSignificantDigitsUsed(boolean useSignificantDigits) {
- this.useSignificantDigits = useSignificantDigits;
- }
-
- /**
- * Sets the Currency object used to display currency amounts. This takes
- * effect immediately, if this format is a currency format. If this format is not a
- * currency format, then the currency object is used if and when this object becomes a
- * currency format through the application of a new pattern.
- *
- * @param theCurrency new currency object to use. Must not be null.
- * @stable ICU 2.2
- */
- @Override
- public void setCurrency(Currency theCurrency) {
- // If we are a currency format, then modify our affixes to
- // encode the currency symbol for the given currency in our
- // locale, and adjust the decimal digits and rounding for the
- // given currency.
-
- super.setCurrency(theCurrency);
- if (theCurrency != null) {
- String s = theCurrency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
- symbols.setCurrency(theCurrency);
- symbols.setCurrencySymbol(s);
- }
-
- if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
- if (theCurrency != null) {
- setRoundingIncrement(theCurrency.getRoundingIncrement(currencyUsage));
- int d = theCurrency.getDefaultFractionDigits(currencyUsage);
- setMinimumFractionDigits(d);
- setMaximumFractionDigits(d);
- }
- if (currencySignCount != CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- // This is not necessary for plural format type
- // because affixes will be resolved in subformat
- expandAffixes(null);
- }
- }
- }
-
- /**
- * Sets the Currency Usage object used to display currency.
- * This takes effect immediately, if this format is a
- * currency format.
- * @param newUsage new currency context object to use.
- * @stable ICU 54
- */
- public void setCurrencyUsage(CurrencyUsage newUsage) {
- if (newUsage == null) {
- throw new NullPointerException("return value is null at method AAA");
- }
- currencyUsage = newUsage;
- Currency theCurrency = this.getCurrency();
-
- // We set rounding/digit based on currency context
- if (theCurrency != null) {
- setRoundingIncrement(theCurrency.getRoundingIncrement(currencyUsage));
- int d = theCurrency.getDefaultFractionDigits(currencyUsage);
- setMinimumFractionDigits(d);
- _setMaximumFractionDigits(d);
- }
- }
-
- /**
- * Returns the Currency Usage object used to display currency
- * @stable ICU 54
- */
- public CurrencyUsage getCurrencyUsage() {
- return currencyUsage;
- }
-
- /**
- * Returns the currency in effect for this formatter. Subclasses should override this
- * method as needed. Unlike getCurrency(), this method should never return null.
- *
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- @Override
- protected Currency getEffectiveCurrency() {
- Currency c = getCurrency();
- if (c == null) {
- c = Currency.getInstance(symbols.getInternationalCurrencySymbol());
- }
- return c;
- }
-
- /**
- * Sets the maximum number of digits allowed in the fraction portion of a number. This
- * override limits the fraction digit count to 340.
- *
- * @see NumberFormat#setMaximumFractionDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMaximumFractionDigits(int newValue) {
- _setMaximumFractionDigits(newValue);
- resetActualRounding();
- }
-
- /*
- * Internal method for DecimalFormat, setting maximum fractional digits
- * without triggering actual rounding recalculated.
- */
- private void _setMaximumFractionDigits(int newValue) {
- super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
- }
-
- /**
- * Sets the minimum number of digits allowed in the fraction portion of a number. This
- * override limits the fraction digit count to 340.
- *
- * @see NumberFormat#setMinimumFractionDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMinimumFractionDigits(int newValue) {
- super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
- }
-
- /**
- * Sets whether {@link #parse(String, ParsePosition)} returns BigDecimal. The
- * default value is false.
- *
- * @param value true if {@link #parse(String, ParsePosition)}
- * returns BigDecimal.
- * @stable ICU 3.6
- */
- public void setParseBigDecimal(boolean value) {
- parseBigDecimal = value;
- }
-
- /**
- * Returns whether {@link #parse(String, ParsePosition)} returns BigDecimal.
- *
- * @return true if {@link #parse(String, ParsePosition)} returns BigDecimal.
- * @stable ICU 3.6
- */
- public boolean isParseBigDecimal() {
- return parseBigDecimal;
- }
-
- /**
- * Set the maximum number of exponent digits when parsing a number.
- * If the limit is set too high, an OutOfMemoryException may be triggered.
- * The default value is 1000.
- * @param newValue the new limit
- * @stable ICU 51
- */
- public void setParseMaxDigits(int newValue) {
- if (newValue > 0) {
- PARSE_MAX_EXPONENT = newValue;
- }
- }
-
- /**
- * Get the current maximum number of exponent digits when parsing a
- * number.
- * @return the maximum number of exponent digits for parsing
- * @stable ICU 51
- */
- public int getParseMaxDigits() {
- return PARSE_MAX_EXPONENT;
- }
-
- private void writeObject(ObjectOutputStream stream) throws IOException {
- // Ticket#6449 Format.Field instances are not serializable. When
- // formatToCharacterIterator is called, attributes (ArrayList) stores
- // FieldPosition instances with NumberFormat.Field. Because NumberFormat.Field is
- // not serializable, we need to clear the contents of the list when writeObject is
- // called. We could remove the field or make it transient, but it will break
- // serialization compatibility.
- attributes.clear();
-
- stream.defaultWriteObject();
- }
-
- /**
- * First, read the default serializable fields from the stream. Then if
- * serialVersionOnStream
is less than 1, indicating that the stream was
- * written by JDK 1.1, initialize useExponentialNotation
to false, since
- * it was not present in JDK 1.1. Finally, set serialVersionOnStream back to the
- * maximum allowed value so that default serialization will work properly if this
- * object is streamed out again.
- */
- private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
- stream.defaultReadObject();
-
- // Bug 4185761 validate fields [Richard/GCL]
-
- // We only need to check the maximum counts because NumberFormat .readObject has
- // already ensured that the maximum is greater than the minimum count.
-
- // Commented for compatibility with previous version, and reserved for further use
- // if (getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS ||
- // getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { throw new
- // InvalidObjectException("Digit count out of range"); }
-
-
- // Truncate the maximumIntegerDigits to DOUBLE_INTEGER_DIGITS and
- // maximumFractionDigits to DOUBLE_FRACTION_DIGITS
-
- if (getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS) {
- setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
- }
- if (getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) {
- _setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
- }
- if (serialVersionOnStream < 2) {
- exponentSignAlwaysShown = false;
- setInternalRoundingIncrement(null);
- roundingMode = BigDecimal.ROUND_HALF_EVEN;
- formatWidth = 0;
- pad = ' ';
- padPosition = PAD_BEFORE_PREFIX;
- if (serialVersionOnStream < 1) {
- // Didn't have exponential fields
- useExponentialNotation = false;
- }
- }
- if (serialVersionOnStream < 3) {
- // Versions prior to 3 do not store a currency object. Create one to match
- // the DecimalFormatSymbols object.
- setCurrencyForSymbols();
- }
- if (serialVersionOnStream < 4) {
- currencyUsage = CurrencyUsage.STANDARD;
- }
- serialVersionOnStream = currentSerialVersion;
- digitList = new DigitList();
-
- if (roundingIncrement != null) {
- setInternalRoundingIncrement(new BigDecimal(roundingIncrement));
- }
- resetActualRounding();
- }
-
- private void setInternalRoundingIncrement(BigDecimal value) {
- roundingIncrementICU = value;
- roundingIncrement = value == null ? null : value.toBigDecimal();
- }
-
- // ----------------------------------------------------------------------
- // INSTANCE VARIABLES
- // ----------------------------------------------------------------------
-
- private transient DigitList digitList = new DigitList();
-
- /**
- * The symbol used as a prefix when formatting positive numbers, e.g. "+".
- *
- * @serial
- * @see #getPositivePrefix
- */
- private String positivePrefix = "";
-
- /**
- * The symbol used as a suffix when formatting positive numbers. This is often an
- * empty string.
- *
- * @serial
- * @see #getPositiveSuffix
- */
- private String positiveSuffix = "";
-
- /**
- * The symbol used as a prefix when formatting negative numbers, e.g. "-".
- *
- * @serial
- * @see #getNegativePrefix
- */
- private String negativePrefix = "-";
-
- /**
- * The symbol used as a suffix when formatting negative numbers. This is often an
- * empty string.
- *
- * @serial
- * @see #getNegativeSuffix
- */
- private String negativeSuffix = "";
-
- /**
- * The prefix pattern for non-negative numbers. This variable corresponds to
- * positivePrefix
.
- *
- * expandAffix()
to
- * positivePrefix
to update the latter to reflect changes in
- * symbols
. If this variable is null
then
- * positivePrefix
is taken as a literal value that does not change when
- * symbols
changes. This variable is always null
for
- * DecimalFormat
objects older than stream version 2 restored from
- * stream.
- *
- * @serial
- */
- // [Richard/GCL]
- private String posPrefixPattern;
-
- /**
- * The suffix pattern for non-negative numbers. This variable corresponds to
- * positiveSuffix
. This variable is analogous to
- * posPrefixPattern
; see that variable for further documentation.
- *
- * @serial
- */
- // [Richard/GCL]
- private String posSuffixPattern;
-
- /**
- * The prefix pattern for negative numbers. This variable corresponds to
- * negativePrefix
. This variable is analogous to
- * posPrefixPattern
; see that variable for further documentation.
- *
- * @serial
- */
- // [Richard/GCL]
- private String negPrefixPattern;
-
- /**
- * The suffix pattern for negative numbers. This variable corresponds to
- * negativeSuffix
. This variable is analogous to
- * posPrefixPattern
; see that variable for further documentation.
- *
- * @serial
- */
- // [Richard/GCL]
- private String negSuffixPattern;
-
- /**
- * Formatter for ChoiceFormat-based currency names. If this field is not null, then
- * delegate to it to format currency symbols.
- * TODO: This is obsolete: Remove, and design extensible serialization. ICU ticket #12090.
- *
- * @since ICU 2.6
- */
- private ChoiceFormat currencyChoice;
-
- /**
- * The multiplier for use in percent, permill, etc.
- *
- * @serial
- * @see #getMultiplier
- */
- private int multiplier = 1;
-
- /**
- * The number of digits between grouping separators in the integer portion of a
- * number. Must be greater than 0 if NumberFormat.groupingUsed
is true.
- *
- * @serial
- * @see #getGroupingSize
- * @see NumberFormat#isGroupingUsed
- */
- private byte groupingSize = 3; // invariant, > 0 if useThousands
-
- /**
- * The secondary grouping size. This is only used for Hindi numerals, which use a
- * primary grouping of 3 and a secondary grouping of 2, e.g., "12,34,567". If this
- * value is less than 1, then secondary grouping is equal to the primary grouping.
- *
- */
- private byte groupingSize2 = 0;
-
- /**
- * If true, forces the decimal separator to always appear in a formatted number, even
- * if the fractional part of the number is zero.
- *
- * @serial
- * @see #isDecimalSeparatorAlwaysShown
- */
- private boolean decimalSeparatorAlwaysShown = false;
-
- /**
- * The DecimalFormatSymbols
object used by this format. It contains the
- * symbols used to format numbers, e.g. the grouping separator, decimal separator, and
- * so on.
- *
- * @serial
- * @see #setDecimalFormatSymbols
- * @see DecimalFormatSymbols
- */
- private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols();
-
- /**
- * True to use significant digits rather than integer and fraction digit counts.
- *
- * @serial
- * @since ICU 3.0
- */
- private boolean useSignificantDigits = false;
-
- /**
- * The minimum number of significant digits to show. Must be >= 1 and <=
- * maxSignificantDigits. Ignored unless useSignificantDigits == true.
- *
- * @serial
- * @since ICU 3.0
- */
- private int minSignificantDigits = 1;
-
- /**
- * The maximum number of significant digits to show. Must be >=
- * minSignficantDigits. Ignored unless useSignificantDigits == true.
- *
- * @serial
- * @since ICU 3.0
- */
- private int maxSignificantDigits = 6;
-
- /**
- * True to force the use of exponential (i.e. scientific) notation
- * when formatting numbers.
- *
- *useExponentialNotation
is not true.
- *
- * useExponentialNotation
is true.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private boolean exponentSignAlwaysShown = false;
-
- /**
- * The value to which numbers are rounded during formatting. For example, if the
- * rounding increment is 0.05, then 13.371 would be formatted as 13.350, assuming 3
- * fraction digits. Has the value null
if rounding is not in effect, or a
- * positive value if rounding is in effect. Default value null
.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- // Note: this is kept in sync with roundingIncrementICU.
- // it is only kept around to avoid a conversion when formatting a java.math.BigDecimal
- private java.math.BigDecimal roundingIncrement = null;
-
- /**
- * The value to which numbers are rounded during formatting. For example, if the
- * rounding increment is 0.05, then 13.371 would be formatted as 13.350, assuming 3
- * fraction digits. Has the value null
if rounding is not in effect, or a
- * positive value if rounding is in effect. Default value null
. WARNING:
- * the roundingIncrement value is the one serialized.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private transient BigDecimal roundingIncrementICU = null;
-
- /**
- * The rounding mode. This value controls any rounding operations which occur when
- * applying a rounding increment or when reducing the number of fraction digits to
- * satisfy a maximum fraction digits limit. The value may assume any of the
- * BigDecimal
rounding mode values. Default value
- * BigDecimal.ROUND_HALF_EVEN
.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private int roundingMode = BigDecimal.ROUND_HALF_EVEN;
-
- /**
- * Operations on BigDecimal
numbers are controlled by a {@link
- * MathContext} object, which provides the context (precision and other information)
- * for the operation. The default MathContext
settings are
- * digits=0, form=PLAIN, lostDigits=false, roundingMode=ROUND_HALF_UP
;
- * these settings perform fixed point arithmetic with unlimited precision, as defined
- * for the original BigDecimal class in Java 1.1 and Java 1.2
- */
- // context for plain unlimited math
- private MathContext mathContext = new MathContext(0, MathContext.PLAIN);
-
- /**
- * The padded format width, or zero if there is no padding. Must be >= 0. Default
- * value zero.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private int formatWidth = 0;
-
- /**
- * The character used to pad the result of format to formatWidth
, if
- * padding is in effect. Default value ' '.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private char pad = ' ';
-
- /**
- * The position in the string at which the pad
character will be
- * inserted, if padding is in effect. Must have a value from
- * PAD_BEFORE_PREFIX
to PAD_AFTER_SUFFIX
. Default value
- * PAD_BEFORE_PREFIX
.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private int padPosition = PAD_BEFORE_PREFIX;
-
- /**
- * True if {@link #parse(String, ParsePosition)} to return BigDecimal rather than
- * Long, Double or BigDecimal except special values. This property is introduced for
- * J2SE 5 compatibility support.
- *
- * @serial
- * @since ICU 3.6
- * @see #setParseBigDecimal(boolean)
- * @see #isParseBigDecimal()
- */
- private boolean parseBigDecimal = false;
-
- /**
- * The currency usage for the NumberFormat(standard or cash usage).
- * It is used as STANDARD by default
- * @since ICU 54
- */
- private CurrencyUsage currencyUsage = CurrencyUsage.STANDARD;
-
- // ----------------------------------------------------------------------
-
- static final int currentSerialVersion = 4;
-
- /**
- * The internal serial version which says which version was written Possible values
- * are:
- *
- *
- *
- *
- *
- * @serial
- */
- private int serialVersionOnStream = currentSerialVersion;
-
- // ----------------------------------------------------------------------
- // CONSTANTS
- // ----------------------------------------------------------------------
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted before the prefix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_BEFORE_PREFIX = 0;
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted after the prefix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_AFTER_PREFIX = 1;
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted before the suffix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_BEFORE_SUFFIX = 2;
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted after the suffix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_AFTER_SUFFIX = 3;
-
- // Constants for characters used in programmatic (unlocalized) patterns.
- static final char PATTERN_ZERO_DIGIT = '0';
- static final char PATTERN_ONE_DIGIT = '1';
- static final char PATTERN_TWO_DIGIT = '2';
- static final char PATTERN_THREE_DIGIT = '3';
- static final char PATTERN_FOUR_DIGIT = '4';
- static final char PATTERN_FIVE_DIGIT = '5';
- static final char PATTERN_SIX_DIGIT = '6';
- static final char PATTERN_SEVEN_DIGIT = '7';
- static final char PATTERN_EIGHT_DIGIT = '8';
- static final char PATTERN_NINE_DIGIT = '9';
- static final char PATTERN_GROUPING_SEPARATOR = ',';
- static final char PATTERN_DECIMAL_SEPARATOR = '.';
- static final char PATTERN_DIGIT = '#';
- static final char PATTERN_SIGNIFICANT_DIGIT = '@';
- static final char PATTERN_EXPONENT = 'E';
- static final char PATTERN_PLUS_SIGN = '+';
- static final char PATTERN_MINUS_SIGN = '-';
-
- // Affix
- private static final char PATTERN_PER_MILLE = '\u2030';
- private static final char PATTERN_PERCENT = '%';
- static final char PATTERN_PAD_ESCAPE = '*';
-
- // Other
- private static final char PATTERN_SEPARATOR = ';';
-
- // Pad escape is package private to allow access by DecimalFormatSymbols.
- // Also plus sign. Also exponent.
-
- /**
- * The CURRENCY_SIGN is the standard Unicode symbol for currency. It is used in
- * patterns and substitued with either the currency symbol, or if it is doubled, with
- * the international currency symbol. If the CURRENCY_SIGN is seen in a pattern, then
- * the decimal separator is replaced with the monetary decimal separator.
- *
- * The CURRENCY_SIGN is not localized.
- */
- private static final char CURRENCY_SIGN = '\u00A4';
-
- private static final char QUOTE = '\'';
-
- /**
- * Upper limit on integer and fraction digits for a Java double [Richard/GCL]
- */
- static final int DOUBLE_INTEGER_DIGITS = 309;
- static final int DOUBLE_FRACTION_DIGITS = 340;
-
- /**
- * When someone turns on scientific mode, we assume that more than this number of
- * digits is due to flipping from some other mode that didn't restrict the maximum,
- * and so we force 1 integer digit. We don't bother to track and see if someone is
- * using exponential notation with more than this number, it wouldn't make sense
- * anyway, and this is just to make sure that someone turning on scientific mode with
- * default settings doesn't end up with lots of zeroes.
- */
- static final int MAX_SCIENTIFIC_INTEGER_DIGITS = 8;
-
- // Proclaim JDK 1.1 serial compatibility.
- private static final long serialVersionUID = 864413376551465018L;
-
- private ArrayListuseExponentialNotation
and minExponentDigits
.
- *
- *