From 495cb271e305cfb399d463f32210a371198f0abf Mon Sep 17 00:00:00 2001 From: Fredrik Roubert Date: Wed, 9 Aug 2017 18:29:05 +0200 Subject: Integrate ICU4J 59.1 with Android patches into android_icu4j. Bug: 62410016 Test: CtsIcuTestCases Test: CtsLibcoreOjTestCases Test: CtsLibcoreTestCases Change-Id: I76ad54bcfa00a8ae21fa33f27ca2f03d4e6c292d --- .../main/java/android/icu/text/DecimalFormat.java | 8427 ++++++-------------- 1 file changed, 2356 insertions(+), 6071 deletions(-) (limited to 'android_icu4j/src/main/java/android/icu/text/DecimalFormat.java') diff --git a/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java b/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java index fa9d0cbf5..d4a421648 100644 --- a/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java +++ b/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java @@ -1,36 +1,33 @@ /* GENERATED SOURCE. DO NOT MODIFY. */ -// © 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 android.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 android.icu.impl.ICUConfig; -import android.icu.impl.PatternProps; -import android.icu.impl.Utility; + +import android.icu.impl.number.Endpoint; +import android.icu.impl.number.Format.SingularFormat; +import android.icu.impl.number.FormatQuantity4; +import android.icu.impl.number.Parse; +import android.icu.impl.number.PatternString; +import android.icu.impl.number.Properties; +import android.icu.impl.number.formatters.PaddingFormat.PadPosition; +import android.icu.impl.number.formatters.PositiveDecimalFormat; +import android.icu.impl.number.formatters.ScientificFormat; +import android.icu.impl.number.rounders.SignificantDigitsRounder; import android.icu.lang.UCharacter; import android.icu.math.BigDecimal; import android.icu.math.MathContext; -import android.icu.text.PluralRules.FixedDecimal; +import android.icu.text.PluralRules.IFixedDecimal; import android.icu.util.Currency; import android.icu.util.Currency.CurrencyUsage; import android.icu.util.CurrencyAmount; @@ -38,248 +35,100 @@ import android.icu.util.ULocale; import android.icu.util.ULocale.Category; /** - * [icu enhancement] ICU's replacement for {@link java.text.DecimalFormat}. Methods, fields, and other functionality specific to ICU are labeled '[icu]'. + * [icu enhancement] ICU's replacement for {@link java.text.DecimalFormat}. Methods, fields, and other functionality specific to ICU are labeled '[icu]'. DecimalFormat is the primary + * concrete subclass of {@link NumberFormat}. 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 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 number formatter for a specific locale (including the default locale), call one of + * NumberFormat's factory methods such as {@link NumberFormat#getInstance}. Do not call + * DecimalFormat constructors directly unless you know what you are doing. + * + *

DecimalFormat aims to comply with the specification UTS #35. Read + * the specification for more information on how all the properties in DecimalFormat fit together. * - * 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. + *

Example Usage

* - *

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: + *

Customize settings on a DecimalFormat instance from the NumberFormat factory: * - *

+ * 
+ * + *
  * NumberFormat f = NumberFormat.getInstance(loc);
  * if (f instanceof DecimalFormat) {
  *     ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true);
- * }
+ * ((DecimalFormat) f).setMinimumGroupingDigits(2); + * } + *
* - *

Example Usage + *

+ * + *

Quick and dirty print out a number using the localized number, currency, and percent format + * for each locale: * - * 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) {}
- *     }
- * }
+ *
+ * for (ULocale uloc : ULocale.getAvailableLocales()) {
+ *     System.out.print(uloc + ":\t");
+ *     System.out.print(NumberFormat.getInstance(uloc).format(1.23));
+ *     System.out.print("\t");
+ *     System.out.print(NumberFormat.getCurrencyInstance(uloc).format(1.23));
+ *     System.out.print("\t");
+ *     System.out.print(NumberFormat.getPercentInstance(uloc).format(1.23));
+ *     System.out.println();
+ * }
+ * 
* - *

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) {}
- * }
+ *

Properties and Symbols

* - *

Patterns

+ *

A DecimalFormat object encapsulates a set of properties and a set of + * symbols. Grouping size, rounding mode, and affixes are examples of properties. Locale + * digits and the characters used for grouping and decimal separators are examples of symbols. * - *

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

To set a custom set of symbols, use {@link #setDecimalFormatSymbols}. Use the various other + * setters in this class to set custom values for the properties. * - *

Special Pattern Characters

+ *

Rounding

* - *

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

DecimalFormat provides three main strategies to specify the position at which numbers should + * be rounded: * - *

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

    + *
  1. Magnitude: Display a fixed number of fraction digits; this is the most + * common form. + *
  2. Increment: Round numbers to the closest multiple of a certain increment, + * such as 0.05. This is common in currencies. + *
  3. Significant Digits: Round numbers such that a fixed number of nonzero + * digits are shown. This is most common in scientific notation. + *
* - *

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

It is also possible to specify the rounding mode to use. The default rounding mode is + * "half even", which rounds numbers to their closest increment, with ties broken in favor of + * trailing numbers being even. For more information, see {@link #setRoundingMode} and the ICU User + * Guide. * - *

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

Pattern Strings

* - *

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#)". + *

A pattern string is a way to serialize some of the available properties for decimal + * formatting. However, not all properties are capable of being serialized into a pattern string; + * see {@link #applyPattern} for more information. * - *

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

Most users should not need to interface with pattern strings directly. * - *

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 "#,##,###,####" == "###,###,####" == - * "##,#,###,####". + *

ICU DecimalFormat aims to follow the specification for pattern strings in UTS #35. + * Refer to that specification for more information on pattern string syntax. * - *

Illegal patterns, such as "#.#.#" or "#.###,###", will cause - * DecimalFormat to throw an {@link IllegalArgumentException} with a message - * that describes the problem. + *

Pattern String BNF

* - *

Pattern BNF

+ * The following BNF is used when parsing the pattern string into property values: * *
  * pattern    := subpattern (';' subpattern)?
@@ -301,5874 +150,2310 @@ import android.icu.util.ULocale.Category;
  *   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: - * - *

- * - *

Parsing

- * - *

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

+ *

The first subpattern is for positive numbers. The second (optional) subpattern is for negative + * numbers. * - *

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

Scientific Notation

- * - *

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

- * - *

Significant Digits

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

Not indicated in the BNF syntax above: * *

* - *

Padding

- * - *

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

- * - *

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

+ *

Thread Safety and Best Practices

* - *

Synchronization

+ *

Starting with ICU 59, instances of DecimalFormat are thread-safe. * - *

DecimalFormat objects are not synchronized. Multiple threads should - * not access one formatter concurrently. + *

Under the hood, DecimalFormat maintains an immutable formatter object that is rebuilt whenever + * any of the property setters are called. It is therefore best practice to call property setters + * only during construction and not when formatting numbers online. * - * @see java.text.Format - * @see NumberFormat - * @author Mark Davis - * @author Alan Liu + * @see java.text.Format + * @see NumberFormat */ 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 - */ - 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); - } - } - - /** - * 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 - */ - 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); - } - } - - /** - * 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 - */ - public DecimalFormat(String pattern, DecimalFormatSymbols symbols) { - createFromPatternAndSymbols(pattern, symbols); - } - - 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); - } - } - - /** - * 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; - */ - 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); - } - - private void create(String pattern, DecimalFormatSymbols inputSymbols, CurrencyPluralInfo info, - int inputStyle) { - if (inputStyle != NumberFormat.PLURALCURRENCYSTYLE) { - createFromPatternAndSymbols(pattern, inputSymbols); + /** New serialization in ICU 59: declare different version from ICU 58. */ + private static final long serialVersionUID = 864413376551465018L; + + /** + * 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; + + //=====================================================================================// + // INSTANCE FIELDS // + //=====================================================================================// + + // 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. + + /** + * 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; + + /** + * The symbols for the current locale. Volatile because threads may read and write at the same + * time. + */ + transient volatile DecimalFormatSymbols symbols; + + /** + * 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; + + /** + * The effective properties as exported from the formatter object. Volatile because threads may + * read and write at the same time. + */ + transient volatile Properties exportedProperties; + + //=====================================================================================// + // CONSTRUCTORS // + //=====================================================================================// + + /** + * Creates a DecimalFormat based on the number pattern and symbols for the default locale. This is + * a convenient way to obtain a DecimalFormat instance when internationalization is not the main + * concern. + * + *

Most users should call the factory methods on NumberFormat, such as {@link + * NumberFormat#getNumberInstance}, which return localized formatter objects, instead of the + * DecimalFormat constructors. + * + * @see NumberFormat#getInstance + * @see NumberFormat#getNumberInstance + * @see NumberFormat#getCurrencyInstance + * @see NumberFormat#getPercentInstance + * @see Category#FORMAT + */ + public DecimalFormat() { + // Use the locale's default pattern + ULocale def = ULocale.getDefault(ULocale.Category.FORMAT); + String pattern = getPattern(def, NumberFormat.NUMBERSTYLE); + symbols = getDefaultSymbols(); + properties = new Properties(); + exportedProperties = new Properties(); + // Regression: ignore pattern rounding information if the pattern has currency symbols. + setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY); + refreshFormatter(); + } + + /** + * Creates a DecimalFormat based on the given pattern, using symbols for the default locale. This + * is a convenient way to obtain a DecimalFormat instance when internationalization is not the + * main concern. + * + *

Most users should call the factory methods on NumberFormat, such as {@link + * NumberFormat#getNumberInstance}, which return localized formatter objects, instead of the + * DecimalFormat constructors. + * + * @param pattern A pattern string such as "#,##0.00" conforming to UTS + * #35. + * @throws IllegalArgumentException if the given pattern is invalid. + * @see NumberFormat#getInstance + * @see NumberFormat#getNumberInstance + * @see NumberFormat#getCurrencyInstance + * @see NumberFormat#getPercentInstance + * @see Category#FORMAT + */ + public DecimalFormat(String pattern) { + symbols = getDefaultSymbols(); + properties = new Properties(); + exportedProperties = new Properties(); + // Regression: ignore pattern rounding information if the pattern has currency symbols. + setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY); + refreshFormatter(); + } + + /** + * Creates a DecimalFormat based on the given pattern and symbols. Use this constructor if you + * want complete control over the behavior of the formatter. + * + *

Most users should call the factory methods on NumberFormat, such as {@link + * NumberFormat#getNumberInstance}, which return localized formatter objects, instead of the + * DecimalFormat constructors. + * + * @param pattern A pattern string such as "#,##0.00" conforming to UTS + * #35. + * @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 + */ + 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. + setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY); + refreshFormatter(); + } + + /** + * Creates a DecimalFormat based on the given pattern and symbols, with additional control over + * the behavior of currency. The style argument determines whether currency rounding rules should + * override the pattern, and the {@link CurrencyPluralInfo} object is used for customizing the + * plural forms used for currency long names. + * + *

Most users should call the factory methods on NumberFormat, such as {@link + * NumberFormat#getNumberInstance}, which return localized formatter objects, instead of the + * DecimalFormat constructors. + * + * @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; + */ + public DecimalFormat( + String pattern, DecimalFormatSymbols symbols, CurrencyPluralInfo infoInput, int style) { + this(pattern, symbols, style); + properties.setCurrencyPluralInfo(infoInput); + refreshFormatter(); + } + + /** 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) { + setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_ALWAYS); + } else { + setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY); + } + refreshFormatter(); + } + + private static DecimalFormatSymbols getDefaultSymbols() { + return DecimalFormatSymbols.getInstance(); + } + + /** + * 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: + * + *

    + *
  1. {@link #setDecimalSeparatorAlwaysShown} + *
  2. {@link #setExponentSignAlwaysShown} + *
  3. {@link #setFormatWidth} + *
  4. {@link #setGroupingSize} + *
  5. {@link #setMultiplier} (percent/permille) + *
  6. {@link #setMaximumFractionDigits} + *
  7. {@link #setMaximumIntegerDigits} + *
  8. {@link #setMaximumSignificantDigits} + *
  9. {@link #setMinimumExponentDigits} + *
  10. {@link #setMinimumFractionDigits} + *
  11. {@link #setMinimumIntegerDigits} + *
  12. {@link #setMinimumSignificantDigits} + *
  13. {@link #setPadPosition} + *
  14. {@link #setPadCharacter} + *
  15. {@link #setRoundingIncrement} + *
  16. {@link #setSecondaryGroupingSize} + *
+ * + * All other settings remain untouched. + * + *

For more information on pattern strings, see UTS #35. + */ + public synchronized void applyPattern(String pattern) { + setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_NEVER); + // 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(); + } + + /** + * Converts the given string to standard notation and then parses it using {@link #applyPattern}. + * This method is provided for backwards compatibility and should not be used in new projects. + * + *

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. + */ + public synchronized void applyLocalizedPattern(String localizedPattern) { + String pattern = PatternString.convertLocalized(localizedPattern, symbols, false); + applyPattern(pattern); + } + + //=====================================================================================// + // CLONE AND SERIALIZE // + //=====================================================================================// + + /***/ + @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; + } + + /** + * Custom serialization: save property bag and symbols; the formatter object can be re-created + * from just that amount of information. + */ + private synchronized 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); + } + + /** + * 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); + + if (serialVersion > 5) { + throw new IOException( + "Cannot deserialize newer android.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("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 { - // 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; - } - - /** - * 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); - } - - /** - * {@inheritDoc} - */ - @Override - public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) { - return format(number, result, fieldPosition, false); - } - - // 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); - } - - // 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; - - // Apply rounding after multiplier - if (roundingDouble > 0.0) { - // number = roundingDouble - // * round(number / roundingDouble, roundingMode, isNegative); - return round( - number, roundingDouble, roundingDoubleReciprocal, roundingMode, - isNegative); - } - return number; - } - - // 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; - } - - // [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); - - 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); - } - } - + // The following fields are ignored: + // "PARSE_MAX_EXPONENT" + // "currencySignCount" + // "style" + // "attributes" + // "currencyChoice" + // "formatPattern" + // "currencyUsage" => ignore this because the old code puts currencyUsage directly into min/max fraction. + } + } + // 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(); + } + } + + //=====================================================================================// + // FORMAT AND PARSE APIS // + //=====================================================================================// + + /** + * {@inheritDoc} + */ + @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; + } + + /** + * {@inheritDoc} + */ + @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; + } + + /** + * {@inheritDoc} + */ + @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; + } + + /** + * {@inheritDoc} + */ + @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; + } + + /** + * {@inheritDoc} + */ + @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; + } + + /** + * {@inheritDoc} + */ + @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; + } + + /** + * {@inheritDoc} + */ + @Override + public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) { + // TODO: This is ugly (although not as ugly as it was in ICU 58). + // Currency should be a free parameter, not in property bag. Fix in ICU 60. + Properties cprops = threadLocalProperties.get(); + SingularFormat fmt = null; + synchronized (this) { + // Use the pre-compiled formatter if possible. Otherwise, copy the properties + // and build our own formatter. + // TODO: Consider using a static format path here. + if (currAmt.getCurrency().equals(properties.getCurrency())) { + fmt = formatter; + } else { + cprops.copyFrom(properties); + } + } + if (fmt == null) { + cprops.setCurrency(currAmt.getCurrency()); + fmt = Endpoint.fromBTA(cprops, symbols); + } + FormatQuantity4 fq = new FormatQuantity4(currAmt.getNumber()); + fmt.format(fq, toAppendTo, pos); + fq.populateUFieldPosition(pos); + return toAppendTo; + } + + /** + * {@inheritDoc} + */ + @Override + public Number parse(String text, ParsePosition parsePosition) { + Properties pprops = threadLocalProperties.get(); + synchronized (this) { + pprops.copyFrom(properties); + } + // Backwards compatibility: use currency parse mode if this is a currency instance + Number result = Parse.parse(text, parsePosition, pprops, symbols); + // Backwards compatibility: return android.icu.math.BigDecimal + if (result instanceof java.math.BigDecimal) { + result = safeConvertBigDecimal((java.math.BigDecimal) result); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) { + try { + CurrencyAmount result = Parse.parseCurrency(text, parsePosition, properties, symbols); + if (result == null) return null; + Number number = result.getNumber(); + // Backwards compatibility: return android.icu.math.BigDecimal + if (number instanceof java.math.BigDecimal) { + number = safeConvertBigDecimal((java.math.BigDecimal) number); + result = new CurrencyAmount(number, result.getCurrency()); + } + return result; + } catch (ParseException e) { + return null; + } + } + + //=====================================================================================// + // GETTERS AND SETTERS // + //=====================================================================================// + + /** + * Returns a copy of the decimal format symbols used by this formatter. + * + * @return desired DecimalFormatSymbols + * @see DecimalFormatSymbols + */ + public synchronized DecimalFormatSymbols getDecimalFormatSymbols() { + return (DecimalFormatSymbols) symbols.clone(); + } + + /** + * Sets the decimal format symbols used by this formatter. The formatter uses a copy of the + * provided symbols. + * + * @param newSymbols desired DecimalFormatSymbols + * @see DecimalFormatSymbols + */ + public synchronized void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { + symbols = (DecimalFormatSymbols) newSymbols.clone(); + refreshFormatter(); + } + + /** + * Affixes: Gets the positive 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 #setPositivePrefix}, the string will be returned + * literally. + * + * @return The string being prepended to positive numbers. + */ + public synchronized String getPositivePrefix() { + String result = exportedProperties.getPositivePrefix(); + return (result == null) ? "" : result; + } + + /** + * 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. + */ + public synchronized void setPositivePrefix(String prefix) { + if (prefix == null) { + throw new NullPointerException(); + } + properties.setPositivePrefix(prefix); + refreshFormatter(); + } + + /** + * 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. + */ + public synchronized String getNegativePrefix() { + String result = exportedProperties.getNegativePrefix(); + return (result == null) ? "" : result; + } + + /** + * 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 prefix The literal string to prepend to negative numbers. + */ + public synchronized void setNegativePrefix(String prefix) { + if (prefix == null) { + throw new NullPointerException(); + } + properties.setNegativePrefix(prefix); + refreshFormatter(); + } + + /** + * 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. + */ + public synchronized String getPositiveSuffix() { + String result = exportedProperties.getPositiveSuffix(); + return (result == null) ? "" : result; + } + + /** + * 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. + */ + public synchronized void setPositiveSuffix(String suffix) { + if (suffix == null) { + throw new NullPointerException(); + } + properties.setPositiveSuffix(suffix); + refreshFormatter(); + } + + /** + * 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. + */ + public synchronized String getNegativeSuffix() { + String result = exportedProperties.getNegativeSuffix(); + return (result == null) ? "" : result; + } + + /** + * 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. + */ + public synchronized void setNegativeSuffix(String suffix) { + if (suffix == null) { + throw new NullPointerException(); + } + properties.setNegativeSuffix(suffix); + refreshFormatter(); + } + + /** + * [icu] Returns whether the sign is being shown on positive numbers. + * + * @see #setSignAlwaysShown + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized boolean getSignAlwaysShown() { + // This is not in the exported properties + return properties.getSignAlwaysShown(); + } + + /** + * Sets whether to always shown the plus sign ('+' in en) on positive numbers. The rules + * in UTS #35 section 3.2.1 will be followed to ensure a locale-aware placement of the sign. + * + *

More specifically, the following strategy will be used to place the plus sign: + * + *

    + *
  1. Patterns without a negative subpattern: The locale's plus sign will be prepended + * to the positive prefix. + *
  2. Patterns with a negative subpattern without a '-' sign (e.g., accounting): The + * locale's plus sign will be prepended to the positive prefix, as in case 1. + *
  3. Patterns with a negative subpattern that has a '-' sign: The locale's plus sign + * will substitute the '-' in the negative subpattern. The positive subpattern will be + * unused. + *
+ * + * This method is designed to be used instead of applying a pattern containing an + * explicit plus sign, such as "+0;-0". The behavior when combining this method with explicit plus + * signs in the pattern is undefined. + * + * @param value true to always show a sign; false to hide the sign on positive numbers. + * @deprecated ICU 59: This API is technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized void setSignAlwaysShown(boolean value) { + properties.setSignAlwaysShown(value); + refreshFormatter(); + } + + /** + * Returns the multiplier being applied to numbers before they are formatted. + * + * @see #setMultiplier + */ + public synchronized int getMultiplier() { + if (properties.getMultiplier() != null) { + return properties.getMultiplier().intValue(); + } else { + return (int) Math.pow(10, properties.getMagnitudeMultiplier()); + } + } + + /** + * 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. + */ + public synchronized void setMultiplier(int multiplier) { + if (multiplier == 0) { + throw new IllegalArgumentException("Multiplier must be nonzero."); + } + + // Try to convert to a magnitude multiplier first + int delta = 0; + int value = multiplier; + while (multiplier != 1) { + delta++; + int temp = value / 10; + if (temp * 10 != value) { + delta = -1; + break; + } + value = temp; + } + if (delta != -1) { + properties.setMagnitudeMultiplier(delta); + } else { + properties.setMultiplier(java.math.BigDecimal.valueOf(multiplier)); + } + refreshFormatter(); + } + + /** + * [icu] Returns the increment to which numbers are being rounded. + * + * @see #setRoundingIncrement + */ + public synchronized java.math.BigDecimal getRoundingIncrement() { + return exportedProperties.getRoundingIncrement(); + } + + /** + * [icu] 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. + * + *

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 + */ + 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; + } + + properties.setRoundingIncrement(increment); + refreshFormatter(); + } + + /** + * [icu] Rounding and Digit Limits: Overload of {@link + * #setRoundingIncrement(java.math.BigDecimal)}. + * + * @param increment The increment to which numbers are to be rounded. + * @see #setRoundingIncrement + */ + public synchronized void setRoundingIncrement(BigDecimal increment) { + java.math.BigDecimal javaBigDecimal = (increment == null) ? null : increment.toBigDecimal(); + setRoundingIncrement(javaBigDecimal); + } + + /** + * [icu] Rounding and Digit Limits: Overload of {@link + * #setRoundingIncrement(java.math.BigDecimal)}. + * + * @param increment The increment to which numbers are to be rounded. + * @see #setRoundingIncrement + */ + 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); + } + } + + /** + * Returns the rounding mode being used to round numbers. + * + * @see #setRoundingMode + */ + @Override + public synchronized int getRoundingMode() { + RoundingMode mode = exportedProperties.getRoundingMode(); + return (mode == null) ? 0 : mode.ordinal(); + } + + /** + * 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. + * + *

+   * df.setRoundingMode(BigDecimal.ROUND_CEILING);
+   * df.setRoundingMode(RoundingMode.CEILING.ordinal());
+   * 
+ * + * @param roundingMode The integer constant rounding mode to use when formatting numbers. + */ + @Override + public synchronized void setRoundingMode(int roundingMode) { + properties.setRoundingMode(RoundingMode.valueOf(roundingMode)); + refreshFormatter(); + } + + /** + * [icu] Returns the {@link java.math.MathContext} being used to round numbers. + * + * @see #setMathContext + */ + public synchronized java.math.MathContext getMathContext() { + java.math.MathContext mathContext = exportedProperties.getMathContext(); + assert mathContext != null; + return mathContext; + } + + /** + * [icu] 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. Most users should call {@link #setRoundingMode} and/or {@link + * #setMaximumSignificantDigits} instead of this method. + * + *

When formatting, since no division is ever performed, the default MathContext is unlimited + * significant digits. However, when division occurs during parsing to correct for percentages and + * multipliers, a MathContext of 34 digits, the IEEE 754R Decimal128 standard, is used by default. + * If you require more than 34 digits when parsing, you can set a custom MathContext using this + * method. + * + * @param mathContext The MathContext to use when rounding numbers. + * @see java.math.MathContext + */ + public synchronized void setMathContext(java.math.MathContext mathContext) { + properties.setMathContext(mathContext); + refreshFormatter(); + } + + // 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; + + /** + * [icu] Returns the {@link android.icu.math.MathContext} being used to round numbers. + * + * @see #setMathContext + */ + public synchronized MathContext getMathContextICU() { + java.math.MathContext mathContext = getMathContext(); + return new MathContext( + mathContext.getPrecision(), + icuMathContextForm, + false, + mathContext.getRoundingMode().ordinal()); + } + + /** + * [icu] Rounding and Digit Limits: Overload of {@link #setMathContext} for + * {@link android.icu.math.MathContext}. + * + * @param mathContextICU The MathContext to use when rounding numbers. + * @see #setMathContext(java.math.MathContext) + */ + 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); + } + + /** + * Returns the effective minimum number of digits before the decimal separator. + * + * @see #setMinimumIntegerDigits + */ + @Override + public synchronized int getMinimumIntegerDigits() { + return exportedProperties.getMinimumIntegerDigits(); + } + + /** + * 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. + * + *

If minimum and maximum integer, fraction, or significant digits conflict with each other, + * the most recently specified value is used. For example, if there is a formatter with minInt=5, + * and then you set maxInt=3, then minInt will be changed to 3. + * + * @param value The minimum number of digits before the decimal separator. + */ + @Override + public synchronized void setMinimumIntegerDigits(int value) { + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int max = properties.getMaximumIntegerDigits(); + if (max >= 0 && max < value) { + properties.setMaximumIntegerDigits(value); + } + properties.setMinimumIntegerDigits(value); + refreshFormatter(); + } + + /** + * Returns the effective maximum number of digits before the decimal separator. + * + * @see #setMaximumIntegerDigits + */ + @Override + public synchronized int getMaximumIntegerDigits() { + return exportedProperties.getMaximumIntegerDigits(); + } + + /** + * 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. + * + *

If minimum and maximum integer, fraction, or significant digits conflict with each other, + * the most recently specified value is used. For example, if there is a formatter with minInt=5, + * and then you set maxInt=3, then minInt will be changed to 3. + * + * @param value The maximum number of digits before the decimal separator. + */ + @Override + public synchronized void setMaximumIntegerDigits(int value) { + int min = properties.getMinimumIntegerDigits(); + if (min >= 0 && min > value) { + properties.setMinimumIntegerDigits(value); + } + properties.setMaximumIntegerDigits(value); + refreshFormatter(); + } + + /** + * Returns the effective minimum number of integer digits after the decimal separator. + * + * @see #setMaximumIntegerDigits + */ + @Override + public synchronized int getMinimumFractionDigits() { + return exportedProperties.getMinimumFractionDigits(); + } + + /** + * 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. + * + *

If minimum and maximum integer, fraction, or significant digits conflict with each other, + * the most recently specified value is used. For example, if there is a formatter with minInt=5, + * and then you set maxInt=3, then minInt will be changed to 3. + * + *

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 + */ + @Override + public synchronized void setMinimumFractionDigits(int value) { + int max = properties.getMaximumFractionDigits(); + if (max >= 0 && max < value) { + properties.setMaximumFractionDigits(value); + } + properties.setMinimumFractionDigits(value); + refreshFormatter(); + } + + /** + * Returns the effective maximum number of integer digits after the decimal separator. + * + * @see #setMaximumIntegerDigits + */ + @Override + public synchronized int getMaximumFractionDigits() { + return exportedProperties.getMaximumFractionDigits(); + } + + /** + * 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. + * + *

If minimum and maximum integer, fraction, or significant digits conflict with each other, + * the most recently specified value is used. For example, if there is a formatter with minInt=5, + * and then you set maxInt=3, then minInt will be changed to 3. + * + * @param value The maximum number of integer digits after the decimal separator. + * @see #setRoundingMode + */ + @Override + public synchronized void setMaximumFractionDigits(int value) { + int min = properties.getMinimumFractionDigits(); + if (min >= 0 && min > value) { + properties.setMinimumFractionDigits(value); + } + properties.setMaximumFractionDigits(value); + refreshFormatter(); + } + + /** + * [icu] Returns whether significant digits are being used in rounding. + * + * @see #setSignificantDigitsUsed + */ + public synchronized boolean areSignificantDigitsUsed() { + return SignificantDigitsRounder.useSignificantDigits(properties); + } + + /** + * [icu] Rounding and Digit Limits: Sets whether significant digits are to be + * used in rounding. + * + *

Calling 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. + */ + 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(); + } + + /** + * [icu] Returns the effective minimum number of significant digits displayed. + * + * @see #setMinimumSignificantDigits + */ + public synchronized int getMinimumSignificantDigits() { + return exportedProperties.getMinimumSignificantDigits(); + } + + /** + * [icu] 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. + * + *

For example, if minimum significant digits is 3 and the number is 1.2, the number will be + * printed as "1.20". + * + *

If minimum and maximum integer, fraction, or significant digits conflict with each other, + * the most recently specified value is used. For example, if there is a formatter with minInt=5, + * and then you set maxInt=3, then minInt will be changed to 3. + * + * @param value The minimum number of significant digits to display. + */ + public synchronized void setMinimumSignificantDigits(int value) { + int max = properties.getMaximumSignificantDigits(); + if (max >= 0 && max < value) { + properties.setMaximumSignificantDigits(value); + } + properties.setMinimumSignificantDigits(value); + refreshFormatter(); + } + + /** + * [icu] Returns the effective maximum number of significant digits displayed. + * + * @see #setMaximumSignificantDigits + */ + public synchronized int getMaximumSignificantDigits() { + return exportedProperties.getMaximumSignificantDigits(); + } + + /** + * [icu] 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". + * + *

If minimum and maximum integer, fraction, or significant digits conflict with each other, + * the most recently specified value is used. For example, if there is a formatter with minInt=5, + * and then you set maxInt=3, then minInt will be changed to 3. + * + *

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 + */ + public synchronized void setMaximumSignificantDigits(int value) { + int min = properties.getMinimumSignificantDigits(); + if (min >= 0 && min > value) { + properties.setMinimumSignificantDigits(value); + } + properties.setMaximumSignificantDigits(value); + refreshFormatter(); + } + + /** + * [icu] Returns the current significant digits mode. + * + * @see #setSignificantDigitsMode + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized SignificantDigitsMode getSignificantDigitsMode() { + return exportedProperties.getSignificantDigitsMode(); + } + + /** + * [icu] 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: + * + *

+   *   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. + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized void setSignificantDigitsMode(SignificantDigitsMode mode) { + properties.setSignificantDigitsMode(mode); + refreshFormatter(); + } + + /** + * Returns the minimum number of characters in formatted output. + * + * @see #setFormatWidth + */ + public synchronized int getFormatWidth() { + return exportedProperties.getFormatWidth(); + } + + /** + * 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. + * + *

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 + */ + public synchronized void setFormatWidth(int width) { + properties.setFormatWidth(width); + refreshFormatter(); + } + + /** + * [icu] Returns the character used for padding. + * + * @see #setPadCharacter + */ + public synchronized char getPadCharacter() { + CharSequence paddingString = exportedProperties.getPadString(); + if (paddingString == null) { + return '.'; // TODO: Is this the correct behavior? + } else { + return paddingString.charAt(0); + } + } + + /** + * [icu] 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 + */ + public synchronized void setPadCharacter(char padChar) { + properties.setPadString(Character.toString(padChar)); + refreshFormatter(); + } + + /** + * [icu] Returns the position used for padding. + * + * @see #setPadPosition + */ + public synchronized int getPadPosition() { + PadPosition loc = exportedProperties.getPadPosition(); + return (loc == null) ? PAD_BEFORE_PREFIX : loc.toOld(); + } + + /** + * [icu] 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: + * + *

+ * + * @param padPos The position used for padding. + * @see #setFormatWidth + */ + public synchronized void setPadPosition(int padPos) { + properties.setPadPosition(PadPosition.fromOld(padPos)); + refreshFormatter(); + } + + /** + * [icu] Returns whether scientific (exponential) notation is enabled on this formatter. + * + * @see #setScientificNotation + */ + public synchronized boolean isScientificNotation() { + return ScientificFormat.useScientificNotation(properties); + } + + /** + * [icu] 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 df.setScientificNotation(true) is functionally equivalent to calling + * df.setMinimumExponentDigits(1). + * + * @param useScientific true to enable scientific notation; false to disable it. + * @see #setMinimumExponentDigits + */ + public synchronized void setScientificNotation(boolean useScientific) { + if (useScientific) { + properties.setMinimumExponentDigits(1); + } else { + properties.setMinimumExponentDigits(Properties.DEFAULT_MINIMUM_EXPONENT_DIGITS); + } + refreshFormatter(); + } + + /** + * [icu] Returns the minimum number of digits printed in the exponent in scientific notation. + * + * @see #setMinimumExponentDigits + */ + public synchronized byte getMinimumExponentDigits() { + return (byte) exportedProperties.getMinimumExponentDigits(); + } + + /** + * [icu] 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. + */ + public synchronized void setMinimumExponentDigits(byte minExpDig) { + properties.setMinimumExponentDigits(minExpDig); + refreshFormatter(); + } + + /** + * [icu] Returns whether the sign (plus or minus) is always printed in scientific notation. + * + * @see #setExponentSignAlwaysShown + */ + public synchronized boolean isExponentSignAlwaysShown() { + return exportedProperties.getExponentSignAlwaysShown(); + } + + /** + * [icu] 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. + */ + public synchronized void setExponentSignAlwaysShown(boolean expSignAlways) { + properties.setExponentSignAlwaysShown(expSignAlways); + refreshFormatter(); + } + + /** + * Returns whether or not grouping separators are being printed in the output. + * + * @see #setGroupingUsed + */ + @Override + public synchronized boolean isGroupingUsed() { + return PositiveDecimalFormat.useGrouping(properties); + } + + /** + * 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). + * + *

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 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 + */ + @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(); + } + + /** + * Returns the primary grouping size in use. + * + * @see #setGroupingSize + */ + public synchronized int getGroupingSize() { + return exportedProperties.getGroupingSize(); + } + + /** + * 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. + * + *

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 + */ + public synchronized void setGroupingSize(int width) { + properties.setGroupingSize(width); + refreshFormatter(); + } + + /** + * [icu] Returns the secondary grouping size in use. + * + * @see #setSecondaryGroupingSize + */ + public synchronized int getSecondaryGroupingSize() { + return exportedProperties.getSecondaryGroupingSize(); + } + + /** + * [icu] 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. + * + *

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 + */ + public synchronized void setSecondaryGroupingSize(int width) { + properties.setSecondaryGroupingSize(width); + refreshFormatter(); + } + + /** + * [icu] Returns the minimum number of digits before grouping is triggered. + * + * @see #setMinimumGroupingDigits + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized int getMinimumGroupingDigits() { + return properties.getMinimumGroupingDigits(); + } + + /** + * [icu] 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. + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized void setMinimumGroupingDigits(int number) { + properties.setMinimumGroupingDigits(number); + refreshFormatter(); + } + + /** + * Returns whether the decimal separator is shown on integers. + * + * @see #setDecimalSeparatorAlwaysShown + */ + public synchronized boolean isDecimalSeparatorAlwaysShown() { + return exportedProperties.getDecimalSeparatorAlwaysShown(); + } + + /** + * 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. + * + *

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. + */ + public synchronized void setDecimalSeparatorAlwaysShown(boolean value) { + properties.setDecimalSeparatorAlwaysShown(value); + refreshFormatter(); + } + + /** + * Returns the user-specified currency. May be null. + * + * @see #setCurrency + * @see DecimalFormatSymbols#getCurrency + */ + @Override + public synchronized Currency getCurrency() { + return properties.getCurrency(); + } + + /** + * Sets the currency to be used when formatting numbers. The effect is twofold: + * + *

    + *
  1. Substitutions for currency symbols in the pattern string will use this currency + *
  2. The rounding mode will obey the rules for this currency (see {@link #setCurrencyUsage}) + *
+ * + * 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. + */ + @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] Returns the strategy for rounding currency amounts. + * + * @see #setCurrencyUsage + */ + 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] 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. + */ + public synchronized void setCurrencyUsage(CurrencyUsage usage) { + properties.setCurrencyUsage(usage); + refreshFormatter(); + } + + /** + * [icu] Returns the current instance of CurrencyPluralInfo. + * + * @see #setCurrencyPluralInfo + */ + public synchronized CurrencyPluralInfo getCurrencyPluralInfo() { + // CurrencyPluralInfo also is not exported. + return properties.getCurrencyPluralInfo(); + } + + /** + * [icu] Sets a custom instance of CurrencyPluralInfo. CurrencyPluralInfo generates pattern + * strings for printing currency long names. + * + *

Most users should not call this method directly. You should instead create + * your formatter via NumberFormat.getInstance(NumberFormat.PLURALCURRENCYSTYLE). + * + * @param newInfo The CurrencyPluralInfo to use when printing currency long names. + */ + public synchronized void setCurrencyPluralInfo(CurrencyPluralInfo newInfo) { + properties.setCurrencyPluralInfo(newInfo); + refreshFormatter(); + } + + /** + * Returns whether {@link #parse} will always return a BigDecimal. + * + * @see #setParseBigDecimal + */ + public synchronized boolean isParseBigDecimal() { + return properties.getParseToBigDecimal(); + } + + /** + * Whether to make {@link #parse} prefer returning a {@link android.icu.math.BigDecimal} when + * possible. For strings corresponding to return values of Infinity, -Infinity, NaN, and -0.0, a + * Double will be returned even if ParseBigDecimal is enabled. + * + * @param value true to cause {@link #parse} to prefer BigDecimal; false to let {@link #parse} + * return additional data types like Long or BigInteger. + */ + public synchronized void setParseBigDecimal(boolean value) { + properties.setParseToBigDecimal(value); + // refreshFormatter() not needed + } + + /** + * Always returns 1000, the default prior to ICU 59. + * + * @deprecated Setting max parse digits has no effect since ICU4J 59. + */ + @Deprecated + public int getParseMaxDigits() { + return 1000; + } + + /** + * @param maxDigits Prior to ICU 59, the maximum number of digits in the output number after + * exponential notation is applied. + * @deprecated Setting max parse digits has no effect since ICU4J 59. + */ + @Deprecated + public void setParseMaxDigits(int maxDigits) {} + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean isParseStrict() { + return properties.getParseMode() == Parse.ParseMode.STRICT; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void setParseStrict(boolean parseStrict) { + Parse.ParseMode mode = parseStrict ? Parse.ParseMode.STRICT : Parse.ParseMode.LENIENT; + properties.setParseMode(mode); + // refreshFormatter() not needed + } + + /** + * {@inheritDoc} + * + * @see #setParseIntegerOnly + */ + @Override + public synchronized boolean isParseIntegerOnly() { + return properties.getParseIntegerOnly(); + } + + /** + * Parsing: {@inheritDoc} + * + *

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. + */ + @Override + public synchronized void setParseIntegerOnly(boolean parseIntegerOnly) { + properties.setParseIntegerOnly(parseIntegerOnly); + // refreshFormatter() not needed + } + + /** + * [icu] Returns whether the presence of a decimal point must match the pattern. + * + * @see #setDecimalPatternMatchRequired + */ + public synchronized boolean isDecimalPatternMatchRequired() { + return properties.getDecimalPatternMatchRequired(); + } + + /** + * [icu] 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}. + * + *

+   * // 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 + */ + public synchronized void setDecimalPatternMatchRequired(boolean value) { + properties.setDecimalPatternMatchRequired(value); + refreshFormatter(); + } + + /** + * [icu] Returns whether to ignore exponents when parsing. + * + * @see #setParseNoExponent + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized boolean getParseNoExponent() { + return properties.getParseNoExponent(); + } + + /** + * [icu] 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. + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized void setParseNoExponent(boolean value) { + properties.setParseNoExponent(value); + refreshFormatter(); + } + + /** + * [icu] Returns whether to force case (uppercase/lowercase) to match when parsing. + * + * @see #setParseNoExponent + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized boolean getParseCaseSensitive() { + return properties.getParseCaseSensitive(); + } + + /** + * [icu] 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. + * @deprecated ICU 59: This API is a technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized void setParseCaseSensitive(boolean value) { + properties.setParseCaseSensitive(value); + refreshFormatter(); + } + + // TODO(sffc): Uncomment for ICU 60 API proposal. + // + // /** + // * {@icu} Returns the strategy used for choosing between grouping and decimal separators when + // * parsing. + // * + // * @see #setParseGroupingMode + // * @category Parsing + // */ + // public synchronized GroupingMode getParseGroupingMode() { + // return properties.getParseGroupingMode(); + // } + // + // /** + // * {@icu} Sets the strategy used during parsing when a code point needs to be interpreted as + // * either a decimal separator or a grouping separator. + // * + // *

The comma, period, space, and apostrophe have different meanings in different locales. For + // * example, in en-US and most American locales, the period is used as a decimal + // * separator, but in es-PY and most European locales, it is used as a grouping separator. + // * + // * Suppose you are in fr-FR the parser encounters the string "1.234". In fr-FR, + // * the grouping is a space and the decimal is a comma. The grouping mode is a mechanism + // * to let you specify whether to accept the string as 1234 (GroupingMode.DEFAULT) or whether to reject it since the separators + // * don't match (GroupingMode.RESTRICTED). + // * + // * When resolving grouping separators, it is the equivalence class of separators that is considered. + // * For example, a period is seen as equal to a fixed set of other period-like characters. + // * + // * @param groupingMode The strategy to use; either DEFAULT or RESTRICTED. + // * @category Parsing + // */ + // public synchronized void setParseGroupingMode(GroupingMode groupingMode) { + // properties.setParseGroupingMode(groupingMode); + // refreshFormatter(); + // } + + //=====================================================================================// + // UTILITIES // + //=====================================================================================// + + /** + * 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. + */ + @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); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized int hashCode() { + return properties.hashCode() ^ symbols.hashCode(); + } + + /** + * Returns the default value of toString() with extra DecimalFormat-specific information appended + * to the end of the string. This extra information is intended for debugging purposes, and the + * format is not guaranteed to be stable. + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(getClass().getName()); + result.append("@"); + result.append(Integer.toHexString(hashCode())); + result.append(" { symbols@"); + result.append(Integer.toHexString(symbols.hashCode())); + synchronized (this) { + properties.toStringBare(result); + } + result.append(" }"); + return result.toString(); + } + + /** + * Serializes this formatter object to a decimal format pattern string. The result of this method + * is guaranteed to be functionally equivalent to the pattern string used to create this + * instance after incorporating values from the setter methods. + * + *

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. + */ + 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 = threadLocalProperties.get().copyFrom(properties); + if (android.icu.impl.number.formatters.CurrencyFormat.useCurrency(properties)) { + tprops.setMinimumFractionDigits(exportedProperties.getMinimumFractionDigits()); + tprops.setMaximumFractionDigits(exportedProperties.getMaximumFractionDigits()); + tprops.setRoundingIncrement(exportedProperties.getRoundingIncrement()); + } + return PatternString.propertiesToString(tprops); + } + + /** + * Calls {@link #toPattern} and converts the string to localized notation. For more information on + * localized notation, see {@link #applyLocalizedPattern}. This method is provided for backwards + * compatibility and should not be used in new projects. + * + * @return A decimal format pattern string in localized notation. + */ + public synchronized String toLocalizedPattern() { + String pattern = toPattern(); + return PatternString.convertLocalized(pattern, symbols, true); + } + + /** + * @deprecated This API is ICU internal only. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public IFixedDecimal getFixedDecimal(double number) { + FormatQuantity4 fq = new FormatQuantity4(number); + formatter.format(fq); + return fq; + } + + private static final ThreadLocal threadLocalProperties = + new ThreadLocal() { + @Override + protected Properties initialValue() { + return new Properties(); + } + }; + + /** 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); + } + + /** + * Converts a java.math.BigDecimal to a android.icu.math.BigDecimal with fallback for numbers + * outside of the range supported by android.icu.math.BigDecimal. + * + * @param number + * @return + */ + private Number safeConvertBigDecimal(java.math.BigDecimal number) { + try { + return new android.icu.math.BigDecimal(number); + } catch (NumberFormatException e) { + if (number.signum() > 0 && number.scale() < 0) { + return Double.POSITIVE_INFINITY; + } else if (number.scale() < 0) { + return Double.NEGATIVE_INFINITY; + } else if (number.signum() < 0) { + return -0.0; + } else { + return 0.0; + } + } + } + + /** + * Updates the property bag with settings from the given pattern. + * + * @param pattern The pattern string to parse. + * @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding + * increment) when parsing the pattern. This may be desirable if a custom rounding mode, such + * as CurrencyUsage, is to be used instead. One of {@link + * PatternString#IGNORE_ROUNDING_ALWAYS}, {@link PatternString#IGNORE_ROUNDING_IF_CURRENCY}, + * or {@link PatternString#IGNORE_ROUNDING_NEVER}. + * @see PatternString#parseToExistingProperties + */ + void setPropertiesFromPattern(String pattern, int ignoreRounding) { + if (pattern == null) { + throw new NullPointerException(); + } + PatternString.parseToExistingProperties(pattern, properties, ignoreRounding); + } + + /** + * @deprecated This API is ICU internal only. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public synchronized void setProperties(PropertySetter func) { + func.set(properties); + refreshFormatter(); + } + + /** + * @deprecated This API is ICU internal only. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public static interface PropertySetter { /** - * 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. * @deprecated This API is ICU internal only. * @hide draft / provisional / internal are hidden on Android */ @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(); - } - - @Deprecated - DigitList toDigitList(double number) { - DigitList result = new DigitList(); - result.set(number, precision(false), false); - return result; - } - - /** - * 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. - * @deprecated This API is ICU internal only. + public void set(Properties props); + } + + /** + * An enum containing the choices for significant digits modes. + * + * @see #setSignificantDigitsMode + * @deprecated ICU 59: This API is technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android + */ + @Deprecated + public static enum SignificantDigitsMode { + /** + * Respect significant digits counts, ignoring the fraction length. + * + * @see DecimalFormat#setSignificantDigitsMode + * @deprecated ICU 59: This API is technical preview. It may change in an upcoming release. * @hide draft / provisional / internal are hidden on Android - */ - @Deprecated - boolean isNumberNegative(double number) { - if (Double.isNaN(number)) { - return false; - } - return isNegative(multiply(number)); - } - - /** - * 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) { - - double div = roundingIncReciprocal == 0.0 ? number / roundingInc : number * - roundingIncReciprocal; - - // do the absolute cases first - - 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: - - // 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; - - /** - */ - // [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. - */ - @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. - */ - @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. */ - @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); - } - } + @Deprecated + OVERRIDE_MAXIMUM_FRACTION, /** - * 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. + * Respect the fraction length, overriding significant digits counts if necessary. * - * @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(); - } else { - return isIntegral ? 0 : getMaximumFractionDigits(); - } - } - - 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); - } - } - - /** - * This is ugly, but don't see a better way to do it without major restructuring of the code. + * @see DecimalFormat#setSignificantDigitsMode + * @deprecated ICU 59: This API is technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android */ - /*package*/ FixedDecimal getFixedDecimal(double number) { - // get the visible fractions and the number of fraction digits. - return getFixedDecimal(number, digitList); - } - - 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); - } - - 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); - } - } - - 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); - } + @Deprecated + RESPECT_MAXIMUM_FRACTION, /** - * Complete the formatting of a finite number. On entry, the - * digitList must be filled in with the correct digits. + * Respect minimum significant digits, overriding fraction length if necessary. + * + * @see DecimalFormat#setSignificantDigitsMode + * @deprecated ICU 59: This API is technical preview. It may change in an upcoming release. + * @hide draft / provisional / internal are hidden on Android */ - 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; - // } - - - - // 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 - } - - int prefixLen = appendAffix(result, isNegative, true, fieldPosition, parseAttr); - - if (useExponentialNotation) { - subformatExponential(result, fieldPosition, parseAttr); - } else { - subformatFixed(result, fieldPosition, isInteger, parseAttr); - } - - int suffixLen = appendAffix(result, isNegative, false, fieldPosition, parseAttr); - addPadding(result, fieldPosition, prefixLen, suffixLen); - return result; - } - - private void subformatFixed(StringBuffer result, - FieldPosition fieldPosition, - boolean isInteger, - boolean parseAttr) { - String[] digits = symbols.getDigitStrings(); - - String grouping = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? - symbols.getGroupingSeparatorString(): symbols.getMonetaryGroupingSeparatorString(); - String decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? - symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString(); - boolean useSigDig = areSignificantDigitsUsed(); - int maxIntDig = getMaximumIntegerDigits(); - int minIntDig = getMinimumIntegerDigits(); - int i; - // [Spark/CDL] Record the integer start index. - int intBegin = result.length(); - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD || - fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setBeginIndex(intBegin); - } - long fractionalDigits = 0; - int fractionalDigitsCount = 0; - boolean recordFractionDigits = false; - - int sigCount = 0; - int minSigDig = getMinimumSignificantDigits(); - int maxSigDig = getMaximumSignificantDigits(); - if (!useSigDig) { - minSigDig = 0; - maxSigDig = Integer.MAX_VALUE; - } - - // Output the integer portion. Here 'count' is the total number of integer - // digits we will display, including both leading zeros required to satisfy - // getMinimumIntegerDigits, and actual digits present in the number. - int count = useSigDig ? Math.max(1, digitList.decimalAt) : minIntDig; - if (digitList.decimalAt > 0 && count < digitList.decimalAt) { - count = digitList.decimalAt; - } - - // Handle the case where getMaximumIntegerDigits() is smaller than the real - // number of integer digits. If this is so, we output the least significant - // max integer digits. For example, the value 1997 printed with 2 max integer - // digits is just "97". - - int digitIndex = 0; // Index into digitList.fDigits[] - if (count > maxIntDig && maxIntDig >= 0) { - count = maxIntDig; - digitIndex = digitList.decimalAt - count; - } - - int sizeBeforeIntegerPart = result.length(); - for (i = count - 1; i >= 0; --i) { - if (i < digitList.decimalAt && digitIndex < digitList.count - && sigCount < maxSigDig) { - // Output a real digit - result.append(digits[digitList.getDigitValue(digitIndex++)]); - ++sigCount; - } else { - // Output a zero (leading or trailing) - result.append(digits[0]); - if (sigCount > 0) { - ++sigCount; - } - } - - // Output grouping separator if necessary. - if (isGroupingPosition(i)) { - result.append(grouping); - // [Spark/CDL] Add grouping separator attribute here. - // Set only for the first instance. - // Length of grouping separator is 1. - if (fieldPosition.getFieldAttribute() == Field.GROUPING_SEPARATOR && - fieldPosition.getBeginIndex() == 0 && fieldPosition.getEndIndex() == 0) { - fieldPosition.setBeginIndex(result.length()-1); - fieldPosition.setEndIndex(result.length()); - } - if (parseAttr) { - addAttribute(Field.GROUPING_SEPARATOR, result.length() - 1, result.length()); - } - } - } - - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD || - fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setEndIndex(result.length()); - } - - // This handles the special case of formatting 0. For zero only, we count the - // zero to the left of the decimal point as one signficant digit. Ordinarily we - // do not count any leading 0's as significant. If the number we are formatting - // is not zero, then either sigCount or digits.getCount() will be non-zero. - if (sigCount == 0 && digitList.count == 0) { - sigCount = 1; - } - - // Determine whether or not there are any printable fractional digits. If - // we've used up the digits we know there aren't. - boolean fractionPresent = (!isInteger && digitIndex < digitList.count) - || (useSigDig ? (sigCount < minSigDig) : (getMinimumFractionDigits() > 0)); - - // If there is no fraction present, and we haven't printed any integer digits, - // then print a zero. Otherwise we won't print _any_ digits, and we won't be - // able to parse this string. - if (!fractionPresent && result.length() == sizeBeforeIntegerPart) - result.append(digits[0]); - // [Spark/CDL] Add attribute for integer part. - if (parseAttr) { - addAttribute(Field.INTEGER, intBegin, result.length()); - } - // Output the decimal separator if we always do so. - if (decimalSeparatorAlwaysShown || fractionPresent) { - if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) { - fieldPosition.setBeginIndex(result.length()); - } - result.append(decimal); - if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) { - fieldPosition.setEndIndex(result.length()); - } - // [Spark/CDL] Add attribute for decimal separator - if (parseAttr) { - addAttribute(Field.DECIMAL_SEPARATOR, result.length() - 1, result.length()); - } - } - - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) { - fieldPosition.setBeginIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) { - fieldPosition.setBeginIndex(result.length()); - } - - // [Spark/CDL] Record the begin index of fraction part. - int fracBegin = result.length(); - recordFractionDigits = fieldPosition instanceof UFieldPosition; - - count = useSigDig ? Integer.MAX_VALUE : getMaximumFractionDigits(); - if (useSigDig && (sigCount == maxSigDig || - (sigCount >= minSigDig && digitIndex == digitList.count))) { - count = 0; - } - for (i = 0; i < count; ++i) { - // Here is where we escape from the loop. We escape if we've output the - // maximum fraction digits (specified in the for expression above). We - // also stop when we've output the minimum digits and either: we have an - // integer, so there is no fractional stuff to display, or we're out of - // significant digits. - if (!useSigDig && i >= getMinimumFractionDigits() && - (isInteger || digitIndex >= digitList.count)) { - break; - } - - // Output leading fractional zeros. These are zeros that come after the - // decimal but before any significant digits. These are only output if - // abs(number being formatted) < 1.0. - if (-1 - i > (digitList.decimalAt - 1)) { - result.append(digits[0]); - if (recordFractionDigits) { - ++fractionalDigitsCount; - fractionalDigits *= 10; - } - continue; - } - - // Output a digit, if we have any precision left, or a zero if we - // don't. We don't want to output noise digits. - if (!isInteger && digitIndex < digitList.count) { - byte digit = digitList.getDigitValue(digitIndex++); - result.append(digits[digit]); - if (recordFractionDigits) { - ++fractionalDigitsCount; - fractionalDigits *= 10; - fractionalDigits += digit; - } - } else { - result.append(digits[0]); - if (recordFractionDigits) { - ++fractionalDigitsCount; - fractionalDigits *= 10; - } - } - - // If we reach the maximum number of significant digits, or if we output - // all the real digits and reach the minimum, then we are done. - ++sigCount; - if (useSigDig && (sigCount == maxSigDig || - (digitIndex == digitList.count && sigCount >= minSigDig))) { - break; - } - } - - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) { - fieldPosition.setEndIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) { - fieldPosition.setEndIndex(result.length()); - } - if (recordFractionDigits) { - ((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits); - } - - // [Spark/CDL] Add attribute information if necessary. - if (parseAttr && (decimalSeparatorAlwaysShown || fractionPresent)) { - addAttribute(Field.FRACTION, fracBegin, result.length()); - } - } - - private void subformatExponential(StringBuffer result, - FieldPosition fieldPosition, - boolean parseAttr) { - String[] digits = symbols.getDigitStringsLocal(); - String decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? - symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString(); - boolean useSigDig = areSignificantDigitsUsed(); - int maxIntDig = getMaximumIntegerDigits(); - int minIntDig = getMinimumIntegerDigits(); - int i; - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - fieldPosition.setBeginIndex(result.length()); - fieldPosition.setEndIndex(-1); - } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) { - fieldPosition.setBeginIndex(-1); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setBeginIndex(result.length()); - fieldPosition.setEndIndex(-1); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) { - fieldPosition.setBeginIndex(-1); - } - - // [Spark/CDL] - // the begin index of integer part - // the end index of integer part - // the begin index of fractional part - int intBegin = result.length(); - int intEnd = -1; - int fracBegin = -1; - int minFracDig = 0; - if (useSigDig) { - maxIntDig = minIntDig = 1; - minFracDig = getMinimumSignificantDigits() - 1; - } else { - minFracDig = getMinimumFractionDigits(); - if (maxIntDig > MAX_SCIENTIFIC_INTEGER_DIGITS) { - maxIntDig = 1; - if (maxIntDig < minIntDig) { - maxIntDig = minIntDig; - } - } - if (maxIntDig > minIntDig) { - minIntDig = 1; - } - } - long fractionalDigits = 0; - int fractionalDigitsCount = 0; - boolean recordFractionDigits = false; - - // Minimum integer digits are handled in exponential format by adjusting the - // exponent. For example, 0.01234 with 3 minimum integer digits is "123.4E-4". - - // Maximum integer digits are interpreted as indicating the repeating - // range. This is useful for engineering notation, in which the exponent is - // restricted to a multiple of 3. For example, 0.01234 with 3 maximum integer - // digits is "12.34e-3". If maximum integer digits are defined and are larger - // than minimum integer digits, then minimum integer digits are ignored. - - int exponent = digitList.decimalAt; - if (maxIntDig > 1 && maxIntDig != minIntDig) { - // A exponent increment is defined; adjust to it. - exponent = (exponent > 0) ? (exponent - 1) / maxIntDig : (exponent / maxIntDig) - 1; - exponent *= maxIntDig; - } else { - // No exponent increment is defined; use minimum integer digits. - // If none is specified, as in "#E0", generate 1 integer digit. - exponent -= (minIntDig > 0 || minFracDig > 0) ? minIntDig : 1; - } - - // We now output a minimum number of digits, and more if there are more - // digits, up to the maximum number of digits. We place the decimal point - // after the "integer" digits, which are the first (decimalAt - exponent) - // digits. - int minimumDigits = minIntDig + minFracDig; - // The number of integer digits is handled specially if the number - // is zero, since then there may be no digits. - int integerDigits = digitList.isZero() ? minIntDig : digitList.decimalAt - exponent; - int totalDigits = digitList.count; - if (minimumDigits > totalDigits) - totalDigits = minimumDigits; - if (integerDigits > totalDigits) - totalDigits = integerDigits; - - for (i = 0; i < totalDigits; ++i) { - if (i == integerDigits) { - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - fieldPosition.setEndIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - fieldPosition.setEndIndex(result.length()); - } - - // [Spark/CDL] Add attribute for integer part - if (parseAttr) { - intEnd = result.length(); - addAttribute(Field.INTEGER, intBegin, result.length()); - } - if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) { - fieldPosition.setBeginIndex(result.length()); - } - result.append(decimal); - if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) { - fieldPosition.setEndIndex(result.length()); - } - // [Spark/CDL] Add attribute for decimal separator - fracBegin = result.length(); - if (parseAttr) { - // Length of decimal separator is 1. - int decimalSeparatorBegin = result.length() - 1; - addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin, - result.length()); - } - // Record field information for caller. - if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) { - fieldPosition.setBeginIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) { - fieldPosition.setBeginIndex(result.length()); - } - recordFractionDigits = fieldPosition instanceof UFieldPosition; - - } - byte digit = (i < digitList.count) ? digitList.getDigitValue(i) : (byte)0; - result.append(digits[digit]); - if (recordFractionDigits) { - ++fractionalDigitsCount; - fractionalDigits *= 10; - fractionalDigits += digit; - } - } - - // For ICU compatibility and format 0 to 0E0 with pattern "#E0" [Richard/GCL] - if (digitList.isZero() && (totalDigits == 0)) { - result.append(digits[0]); - } - - // add the decimal separator if it is to be always shown AND there are no decimal digits - if ((fracBegin == -1) && this.decimalSeparatorAlwaysShown) { - if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) { - fieldPosition.setBeginIndex(result.length()); - } - result.append(decimal); - if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) { - fieldPosition.setEndIndex(result.length()); - } - if (parseAttr) { - // Length of decimal separator is 1. - int decimalSeparatorBegin = result.length() - 1; - addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin, result.length()); - } - } - - // Record field information - if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { - if (fieldPosition.getEndIndex() < 0) { - fieldPosition.setEndIndex(result.length()); - } - } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) { - if (fieldPosition.getBeginIndex() < 0) { - fieldPosition.setBeginIndex(result.length()); - } - fieldPosition.setEndIndex(result.length()); - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { - if (fieldPosition.getEndIndex() < 0) { - fieldPosition.setEndIndex(result.length()); - } - } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) { - if (fieldPosition.getBeginIndex() < 0) { - fieldPosition.setBeginIndex(result.length()); - } - fieldPosition.setEndIndex(result.length()); - } - if (recordFractionDigits) { - ((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits); - } - - // [Spark/CDL] Calculate the end index of integer part and fractional - // part if they are not properly processed yet. - if (parseAttr) { - if (intEnd < 0) { - addAttribute(Field.INTEGER, intBegin, result.length()); - } - if (fracBegin > 0) { - addAttribute(Field.FRACTION, fracBegin, result.length()); - } - } - - // The exponent is output using the pattern-specified minimum exponent - // digits. There is no maximum limit to the exponent digits, since truncating - // the exponent would result in an unacceptable inaccuracy. - if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SYMBOL) { - fieldPosition.setBeginIndex(result.length()); - } - - result.append(symbols.getExponentSeparator()); - if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SYMBOL) { - fieldPosition.setEndIndex(result.length()); - } - // [Spark/CDL] For exponent symbol, add an attribute. - if (parseAttr) { - addAttribute(Field.EXPONENT_SYMBOL, result.length() - - symbols.getExponentSeparator().length(), result.length()); - } - // For zero values, we force the exponent to zero. We must do this here, and - // not earlier, because the value is used to determine integer digit count - // above. - if (digitList.isZero()) - exponent = 0; - - boolean negativeExponent = exponent < 0; - if (negativeExponent) { - exponent = -exponent; - if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { - fieldPosition.setBeginIndex(result.length()); - } - result.append(symbols.getMinusSignString()); - if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { - fieldPosition.setEndIndex(result.length()); - } - // [Spark/CDL] If exponent has sign, then add an exponent sign - // attribute. - if (parseAttr) { - // Length of exponent sign is 1. - addAttribute(Field.EXPONENT_SIGN, result.length() - 1, result.length()); - } - } else if (exponentSignAlwaysShown) { - if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { - fieldPosition.setBeginIndex(result.length()); - } - result.append(symbols.getPlusSignString()); - if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { - fieldPosition.setEndIndex(result.length()); - } - // [Spark/CDL] Add an plus sign attribute. - if (parseAttr) { - // Length of exponent sign is 1. - int expSignBegin = result.length() - 1; - addAttribute(Field.EXPONENT_SIGN, expSignBegin, result.length()); - } - } - int expBegin = result.length(); - digitList.set(exponent); - { - int expDig = minExponentDigits; - if (useExponentialNotation && expDig < 1) { - expDig = 1; - } - for (i = digitList.decimalAt; i < expDig; ++i) - result.append(digits[0]); - } - for (i = 0; i < digitList.decimalAt; ++i) { - result.append((i < digitList.count) ? digits[digitList.getDigitValue(i)] - : digits[0]); - } - // [Spark/CDL] Add attribute for exponent part. - if (fieldPosition.getFieldAttribute() == Field.EXPONENT) { - fieldPosition.setBeginIndex(expBegin); - fieldPosition.setEndIndex(result.length()); - } - if (parseAttr) { - addAttribute(Field.EXPONENT, expBegin, result.length()); - } - } - - private final void addPadding(StringBuffer result, FieldPosition fieldPosition, int prefixLen, - int suffixLen) { - if (formatWidth > 0) { - int len = formatWidth - result.length(); - if (len > 0) { - char[] padding = new char[len]; - for (int i = 0; i < len; ++i) { - padding[i] = pad; - } - switch (padPosition) { - case PAD_AFTER_PREFIX: - result.insert(prefixLen, padding); - break; - case PAD_BEFORE_PREFIX: - result.insert(0, padding); - break; - case PAD_BEFORE_SUFFIX: - result.insert(result.length() - suffixLen, padding); - break; - case PAD_AFTER_SUFFIX: - result.append(padding); - break; - } - if (padPosition == PAD_BEFORE_PREFIX || padPosition == PAD_AFTER_PREFIX) { - fieldPosition.setBeginIndex(fieldPosition.getBeginIndex() + len); - fieldPosition.setEndIndex(fieldPosition.getEndIndex() + len); - } - } - } - } - - /** - * Parses the given string, returning a 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 - */ - @Override - public Number parse(String text, ParsePosition parsePosition) { - return (Number) parse(text, parsePosition, null); - } - - /** - * 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 - */ - @Override - public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) { - Currency[] currency = new Currency[1]; - return (CurrencyAmount) parse(text.toString(), pos, currency); - } - - /** - * 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(); - - // Handle NaN as a special case: - - // 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); - } - - // NaN parse failed; start over - i = backup; - - 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; - } - } - - Number n = null; - - // Handle infinity - if (status[STATUS_INFINITE]) { - n = new Double(status[STATUS_POSITIVE] ? Double.POSITIVE_INFINITY : - Double.NEGATIVE_INFINITY); - } - - // Handle underflow - else if (status[STATUS_UNDERFLOW]) { - n = status[STATUS_POSITIVE] ? new Double("0.0") : new Double("-0.0"); - } - - // 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; - } - - 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; - } - - // 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(); - - // save the current pattern, since it will be changed by - // applyPatternWithoutExpandAffix - String savedFormatPattern = formatPattern; - - // CURRENCYSTYLE and ISOCURRENCYSTYLE should have the same prefix and suffix, so, - // only need to save one of them. Here, chose onlyApplyPatternWithoutExpandAffix - // without saving the actualy pattern in 'pattern' data member. TODO: is it uloc? - applyPatternWithoutExpandAffix(getPattern(symbols.getULocale(), NumberFormat.CURRENCYSTYLE), - false); - AffixForCurrency affixes = new AffixForCurrency( - negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern, - Currency.SYMBOL_NAME); - affixPatternsForCurrency.add(affixes); - - // add plural pattern - Iterator iter = currencyPluralInfo.pluralPatternIterator(); - Set currencyUnitPatternSet = new HashSet(); - while (iter.hasNext()) { - String pluralCount = iter.next(); - String currencyPattern = currencyPluralInfo.getCurrencyPluralPattern(pluralCount); - if (currencyPattern != null && - currencyUnitPatternSet.contains(currencyPattern) == false) { - currencyUnitPatternSet.add(currencyPattern); - applyPatternWithoutExpandAffix(currencyPattern, false); - affixes = new AffixForCurrency(negPrefixPattern, negSuffixPattern, posPrefixPattern, - posSuffixPattern, Currency.LONG_NAME); - affixPatternsForCurrency.add(affixes); - } - } - // reset pattern back - formatPattern = savedFormatPattern; - } - - // currency formatting style options - private static final int CURRENCY_SIGN_COUNT_ZERO = 0; - private static final int CURRENCY_SIGN_COUNT_IN_SYMBOL_FORMAT = 1; - private static final int CURRENCY_SIGN_COUNT_IN_ISO_FORMAT = 2; - private static final int CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT = 3; - - private static final int STATUS_INFINITE = 0; - private static final int STATUS_POSITIVE = 1; - private static final int STATUS_UNDERFLOW = 2; - private static final int STATUS_LENGTH = 3; - - private static final UnicodeSet dotEquivalents = new UnicodeSet( - //"[.\u2024\u3002\uFE12\uFE52\uFF0E\uFF61]" - 0x002E, 0x002E, - 0x2024, 0x2024, - 0x3002, 0x3002, - 0xFE12, 0xFE12, - 0xFE52, 0xFE52, - 0xFF0E, 0xFF0E, - 0xFF61, 0xFF61).freeze(); - - private static final UnicodeSet commaEquivalents = new UnicodeSet( - //"[,\u060C\u066B\u3001\uFE10\uFE11\uFE50\uFE51\uFF0C\uFF64]" - 0x002C, 0x002C, - 0x060C, 0x060C, - 0x066B, 0x066B, - 0x3001, 0x3001, - 0xFE10, 0xFE11, - 0xFE50, 0xFE51, - 0xFF0C, 0xFF0C, - 0xFF64, 0xFF64).freeze(); - -// private static final UnicodeSet otherGroupingSeparators = new UnicodeSet( -// //"[\\ '\u00A0\u066C\u2000-\u200A\u2018\u2019\u202F\u205F\u3000\uFF07]" -// 0x0020, 0x0020, -// 0x0027, 0x0027, -// 0x00A0, 0x00A0, -// 0x066C, 0x066C, -// 0x2000, 0x200A, -// 0x2018, 0x2019, -// 0x202F, 0x202F, -// 0x205F, 0x205F, -// 0x3000, 0x3000, -// 0xFF07, 0xFF07).freeze(); - - private static final UnicodeSet strictDotEquivalents = new UnicodeSet( - //"[.\u2024\uFE52\uFF0E\uFF61]" - 0x002E, 0x002E, - 0x2024, 0x2024, - 0xFE52, 0xFE52, - 0xFF0E, 0xFF0E, - 0xFF61, 0xFF61).freeze(); - - private static final UnicodeSet strictCommaEquivalents = new UnicodeSet( - //"[,\u066B\uFE10\uFE50\uFF0C]" - 0x002C, 0x002C, - 0x066B, 0x066B, - 0xFE10, 0xFE10, - 0xFE50, 0xFE50, - 0xFF0C, 0xFF0C).freeze(); - -// private static final UnicodeSet strictOtherGroupingSeparators = new UnicodeSet( -// //"[\\ '\u00A0\u066C\u2000-\u200A\u2018\u2019\u202F\u205F\u3000\uFF07]" -// 0x0020, 0x0020, -// 0x0027, 0x0027, -// 0x00A0, 0x00A0, -// 0x066C, 0x066C, -// 0x2000, 0x200A, -// 0x2018, 0x2019, -// 0x202F, 0x202F, -// 0x205F, 0x205F, -// 0x3000, 0x3000, -// 0xFF07, 0xFF07).freeze(); - - private static final UnicodeSet defaultGroupingSeparators = - // new UnicodeSet(dotEquivalents).addAll(commaEquivalents) - // .addAll(otherGroupingSeparators).freeze(); - new UnicodeSet( - 0x0020, 0x0020, - 0x0027, 0x0027, - 0x002C, 0x002C, - 0x002E, 0x002E, - 0x00A0, 0x00A0, - 0x060C, 0x060C, - 0x066B, 0x066C, - 0x2000, 0x200A, - 0x2018, 0x2019, - 0x2024, 0x2024, - 0x202F, 0x202F, - 0x205F, 0x205F, - 0x3000, 0x3002, - 0xFE10, 0xFE12, - 0xFE50, 0xFE52, - 0xFF07, 0xFF07, - 0xFF0C, 0xFF0C, - 0xFF0E, 0xFF0E, - 0xFF61, 0xFF61, - 0xFF64, 0xFF64).freeze(); - - private static final UnicodeSet strictDefaultGroupingSeparators = - // new UnicodeSet(strictDotEquivalents).addAll(strictCommaEquivalents) - // .addAll(strictOtherGroupingSeparators).freeze(); - new UnicodeSet( - 0x0020, 0x0020, - 0x0027, 0x0027, - 0x002C, 0x002C, - 0x002E, 0x002E, - 0x00A0, 0x00A0, - 0x066B, 0x066C, - 0x2000, 0x200A, - 0x2018, 0x2019, - 0x2024, 0x2024, - 0x202F, 0x202F, - 0x205F, 0x205F, - 0x3000, 0x3000, - 0xFE10, 0xFE10, - 0xFE50, 0xFE50, - 0xFE52, 0xFE52, - 0xFF07, 0xFF07, - 0xFF0C, 0xFF0C, - 0xFF0E, 0xFF0E, - 0xFF61, 0xFF61).freeze(); - - static final UnicodeSet minusSigns = - new UnicodeSet( - 0x002D, 0x002D, - 0x207B, 0x207B, - 0x208B, 0x208B, - 0x2212, 0x2212, - 0x2796, 0x2796, - 0xFE63, 0xFE63, - 0xFF0D, 0xFF0D).freeze(); - - static final UnicodeSet plusSigns = - new UnicodeSet( - 0x002B, 0x002B, - 0x207A, 0x207A, - 0x208A, 0x208A, - 0x2795, 0x2795, - 0xFB29, 0xFB29, - 0xFE62, 0xFE62, - 0xFF0B, 0xFF0B).freeze(); - - // equivalent grouping and decimal support - static final boolean skipExtendedSeparatorParsing = ICUConfig.get( - "android.icu.text.DecimalFormat.SkipExtendedSeparatorParsing", "false") - .equals("true"); - - // allow control of requiring a matching decimal point when parsing - boolean parseRequireDecimalPoint = false; - - // When parsing a number with big exponential value, it requires to transform the - // value into a string representation to construct BigInteger instance. We want to - // set the maximum size because it can easily trigger OutOfMemoryException. - // PARSE_MAX_EXPONENT is currently set to 1000 (See getParseMaxDigits()), - // which is much bigger than MAX_VALUE of Double ( See the problem reported by ticket#5698 - private int PARSE_MAX_EXPONENT = 1000; - - /** - * Parses the given text into a number. The text is parsed beginning at parsePosition, - * until an unparseable character is seen. - * - * @param text the string to parse. - * @param parsePosition the position at which to being parsing. Upon return, the first - * unparseable character. - * @param digits the DigitList to set to the parsed value. - * @param status Upon return contains boolean status flags indicating whether the - * value was infinite and whether it was positive. - * @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. - * @param negPrefix negative prefix pattern - * @param negSuffix negative suffix pattern - * @param posPrefix positive prefix pattern - * @param negSuffix negative suffix pattern - * @param parseComplexCurrency whether it is complex currency parsing or not. - * @param type type of currency to parse against, LONG_NAME only or not. - */ - private final boolean subparse( - String text, ParsePosition parsePosition, DigitList digits, - boolean status[], Currency currency[], String negPrefix, String negSuffix, String posPrefix, - String posSuffix, boolean parseComplexCurrency, int type) { - - int position = parsePosition.getIndex(); - int oldStart = parsePosition.getIndex(); - - // Match padding before prefix - if (formatWidth > 0 && padPosition == PAD_BEFORE_PREFIX) { - position = skipPadding(text, position); - } - - // Match positive and negative prefixes; prefer longest match. - int posMatch = compareAffix(text, position, false, true, posPrefix, parseComplexCurrency, type, currency); - int negMatch = compareAffix(text, position, true, true, negPrefix, parseComplexCurrency, type, currency); - if (posMatch >= 0 && negMatch >= 0) { - if (posMatch > negMatch) { - negMatch = -1; - } else if (negMatch > posMatch) { - posMatch = -1; - } - } - if (posMatch >= 0) { - position += posMatch; - } else if (negMatch >= 0) { - position += negMatch; - } else { - parsePosition.setErrorIndex(position); - return false; - } - - // Match padding after prefix - if (formatWidth > 0 && padPosition == PAD_AFTER_PREFIX) { - position = skipPadding(text, position); - } - - // process digits or Inf, find decimal position - status[STATUS_INFINITE] = false; - if (text.regionMatches(position, symbols.getInfinity(), 0, - symbols.getInfinity().length())) { - position += symbols.getInfinity().length(); - status[STATUS_INFINITE] = true; - } else { - // We now have a string of digits, possibly with grouping symbols, and decimal - // points. We want to process these into a DigitList. We don't want to put a - // bunch of leading zeros into the DigitList though, so we keep track of the - // location of the decimal point, put only significant digits into the - // DigitList, and adjust the exponent as needed. - - digits.decimalAt = digits.count = 0; - String decimal = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ? - symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString(); - String grouping = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ? - symbols.getGroupingSeparatorString() : symbols.getMonetaryGroupingSeparatorString(); - - String exponentSep = symbols.getExponentSeparator(); - boolean sawDecimal = false; - boolean sawGrouping = false; - boolean sawDigit = false; - long exponent = 0; // Set to the exponent value, if any - - // strict parsing - boolean strictParse = isParseStrict(); - boolean strictFail = false; // did we exit with a strict parse failure? - int lastGroup = -1; // where did we last see a grouping separator? - int groupedDigitCount = 0; // tracking count of digits delimited by grouping separator - int gs2 = groupingSize2 == 0 ? groupingSize : groupingSize2; - - UnicodeSet decimalEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY : - getEquivalentDecimals(decimal, strictParse); - UnicodeSet groupEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY : - (strictParse ? strictDefaultGroupingSeparators : defaultGroupingSeparators); - - // We have to track digitCount ourselves, because digits.count will pin when - // the maximum allowable digits is reached. - int digitCount = 0; - - int backup = -1; // used for preserving the last confirmed position - int[] parsedDigit = {-1}; // allocates int[1] for parsing a single digit - - while (position < text.length()) { - // Check if the sequence at the current position matches a decimal digit - int matchLen = matchesDigit(text, position, parsedDigit); - if (matchLen > 0) { - // matched a digit - // Cancel out backup setting (see grouping handler below) - if (backup != -1) { - if (strictParse) { - // comma followed by digit, so group before comma is a secondary - // group. If there was a group separator before that, the group - // must == the secondary group length, else it can be <= the the - // secondary group length. - if ((lastGroup != -1 && groupedDigitCount != gs2) - || (lastGroup == -1 && groupedDigitCount > gs2)) { - strictFail = true; - break; - } - } - lastGroup = backup; - groupedDigitCount = 0; - } - - groupedDigitCount++; - position += matchLen; - backup = -1; - sawDigit = true; - if (parsedDigit[0] == 0 && digits.count == 0) { - // Handle leading zeros - if (!sawDecimal) { - // Ignore leading zeros in integer part of number. - continue; - } - // If we have seen the decimal, but no significant digits yet, - // then we account for leading zeros by decrementing the - // digits.decimalAt into negative values. - --digits.decimalAt; - } else { - ++digitCount; - digits.append((char) (parsedDigit[0] + '0')); - } - continue; - } - - // Check if the sequence at the current position matches locale's decimal separator - int decimalStrLen = decimal.length(); - if (text.regionMatches(position, decimal, 0, decimalStrLen)) { - // matched a decimal separator - if (strictParse) { - if (backup != -1 || - (lastGroup != -1 && groupedDigitCount != groupingSize)) { - strictFail = true; - break; - } - } - - // If we're only parsing integers, or if we ALREADY saw the decimal, - // then don't parse this one. - if (isParseIntegerOnly() || sawDecimal) { - break; - } - - digits.decimalAt = digitCount; // Not digits.count! - sawDecimal = true; - position += decimalStrLen; - continue; - } - - if (isGroupingUsed()) { - // Check if the sequence at the current position matches locale's grouping separator - int groupingStrLen = grouping.length(); - if (text.regionMatches(position, grouping, 0, groupingStrLen)) { - if (sawDecimal) { - break; - } - - if (strictParse) { - if ((!sawDigit || backup != -1)) { - // leading group, or two group separators in a row - strictFail = true; - break; - } - } - - // Ignore grouping characters, if we are using them, but require that - // they be followed by a digit. Otherwise we backup and reprocess - // them. - backup = position; - position += groupingStrLen; - sawGrouping = true; - continue; - } - } - - // Check if the code point at the current position matches one of decimal/grouping equivalent group chars - int cp = text.codePointAt(position); - if (!sawDecimal && decimalEquiv.contains(cp)) { - // matched a decimal separator - if (strictParse) { - if (backup != -1 || - (lastGroup != -1 && groupedDigitCount != groupingSize)) { - strictFail = true; - break; - } - } - - // If we're only parsing integers, or if we ALREADY saw the decimal, - // then don't parse this one. - if (isParseIntegerOnly()) { - break; - } - - digits.decimalAt = digitCount; // Not digits.count! - - // Once we see a decimal separator character, we only accept that - // decimal separator character from then on. - decimal = String.valueOf(Character.toChars(cp)); - - sawDecimal = true; - position += Character.charCount(cp); - continue; - } - - if (isGroupingUsed() && !sawGrouping && groupEquiv.contains(cp)) { - // matched a grouping separator - if (sawDecimal) { - break; - } - - if (strictParse) { - if ((!sawDigit || backup != -1)) { - // leading group, or two group separators in a row - strictFail = true; - break; - } - } - - // Once we see a grouping character, we only accept that grouping - // character from then on. - grouping = String.valueOf(Character.toChars(cp)); - - // Ignore grouping characters, if we are using them, but require that - // they be followed by a digit. Otherwise we backup and reprocess - // them. - backup = position; - position += Character.charCount(cp); - sawGrouping = true; - continue; - } - - // Check if the sequence at the current position matches locale's exponent separator - int exponentSepStrLen = exponentSep.length(); - if (text.regionMatches(true, position, exponentSep, 0, exponentSepStrLen)) { - // parse sign, if present - boolean negExp = false; - int pos = position + exponentSep.length(); - if (pos < text.length()) { - String plusSign = symbols.getPlusSignString(); - String minusSign = symbols.getMinusSignString(); - if (text.regionMatches(pos, plusSign, 0, plusSign.length())) { - pos += plusSign.length(); - } else if (text.regionMatches(pos, minusSign, 0, minusSign.length())) { - pos += minusSign.length(); - negExp = true; - } - } - - DigitList exponentDigits = new DigitList(); - exponentDigits.count = 0; - while (pos < text.length()) { - int digitMatchLen = matchesDigit(text, pos, parsedDigit); - if (digitMatchLen > 0) { - exponentDigits.append((char) (parsedDigit[0] + '0')); - pos += digitMatchLen; - } else { - break; - } - } - - if (exponentDigits.count > 0) { - // defer strict parse until we know we have a bona-fide exponent - if (strictParse && sawGrouping) { - strictFail = true; - break; - } - - // Quick overflow check for exponential part. Actual limit check - // will be done later in this code. - if (exponentDigits.count > 10 /* maximum decimal digits for int */) { - if (negExp) { - // set underflow flag - status[STATUS_UNDERFLOW] = true; - } else { - // set infinite flag - status[STATUS_INFINITE] = true; - } - } else { - exponentDigits.decimalAt = exponentDigits.count; - exponent = exponentDigits.getLong(); - if (negExp) { - exponent = -exponent; - } - } - position = pos; // Advance past the exponent - } - - break; // Whether we fail or succeed, we exit this loop - } - - // All other cases, stop parsing - break; - } - - if (digits.decimalAt == 0 && isDecimalPatternMatchRequired()) { - if (this.formatPattern.indexOf(decimal) != -1) { - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - return false; - } - } - - if (backup != -1) - position = backup; - - // If there was no decimal point we have an integer - if (!sawDecimal) { - digits.decimalAt = digitCount; // Not digits.count! - } - - // check for strict parse errors - if (strictParse && !sawDecimal) { - if (lastGroup != -1 && groupedDigitCount != groupingSize) { - strictFail = true; - } - } - if (strictFail) { - // only set with strictParse and a leading zero error leading zeros are an - // error with strict parsing except immediately before nondigit (except - // group separator followed by digit), or end of text. - - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - return false; - } - - // Adjust for exponent, if any - exponent += digits.decimalAt; - if (exponent < -getParseMaxDigits()) { - status[STATUS_UNDERFLOW] = true; - } else if (exponent > getParseMaxDigits()) { - status[STATUS_INFINITE] = true; - } else { - digits.decimalAt = (int) exponent; - } - - // If none of the text string was recognized. For example, parse "x" with - // pattern "#0.00" (return index and error index both 0) parse "$" with - // pattern "$#0.00". (return index 0 and error index 1). - if (!sawDigit && digitCount == 0) { - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(oldStart); - return false; - } - } - - // Match padding before suffix - if (formatWidth > 0 && padPosition == PAD_BEFORE_SUFFIX) { - position = skipPadding(text, position); - } - - // Match positive and negative suffixes; prefer longest match. - if (posMatch >= 0) { - posMatch = compareAffix(text, position, false, false, posSuffix, parseComplexCurrency, type, currency); - } - if (negMatch >= 0) { - negMatch = compareAffix(text, position, true, false, negSuffix, parseComplexCurrency, type, currency); - } - if (posMatch >= 0 && negMatch >= 0) { - if (posMatch > negMatch) { - negMatch = -1; - } else if (negMatch > posMatch) { - posMatch = -1; - } - } - - // Fail if neither or both - if ((posMatch >= 0) == (negMatch >= 0)) { - parsePosition.setErrorIndex(position); - return false; - } - - position += (posMatch >= 0 ? posMatch : negMatch); - - // Match padding after suffix - if (formatWidth > 0 && padPosition == PAD_AFTER_SUFFIX) { - position = skipPadding(text, position); - } - - parsePosition.setIndex(position); - - status[STATUS_POSITIVE] = (posMatch >= 0); - - if (parsePosition.getIndex() == oldStart) { - parsePosition.setErrorIndex(position); - return false; - } - return true; - } - - /** - * Check if the substring at the specified position matches a decimal digit. - * If matched, this method sets the decimal value to 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(); - - // 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; - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * 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); - } - - } - - /** - * Check for bidi marks: LRM, RLM, ALM - */ - private static boolean isBidiMark(int c) { - return (c==0x200E || c==0x200F || c==0x061C); - } - - /** - * 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; - } - - 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 buf.toString(); - } - - /** - * 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; - } - } - - // Advance over run in trimmedAffix - i = skipPatternWhiteSpace(trimmedAffix, i); - - // 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; - } - - private static boolean equalWithSignCompatibility(int lhs, int rhs) { - return lhs == rhs - || (minusSigns.contains(lhs) && minusSigns.contains(rhs)) - || (plusSigns.contains(lhs) && plusSigns.contains(rhs)); - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * 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; - } - - String affix = null; - - 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); - - // 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; - } - - if (affix != null) { - pos = match(text, pos, affix); - continue; - } - - pos = match(text, pos, c); - if (PatternProps.isWhiteSpace(c)) { - i = skipPatternWhiteSpace(affixPat, i); - } - } - - return pos - start; - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * Returns a copy of the decimal format symbols used by this format. - * - * @return desired DecimalFormatSymbols - * @see DecimalFormatSymbols - */ - public DecimalFormatSymbols getDecimalFormatSymbols() { - try { - // don't allow multiple references - return (DecimalFormatSymbols) symbols.clone(); - } catch (Exception foo) { - return null; // should never happen - } - } - - /** - * Sets the decimal format symbols used by this format. The format uses a copy of the - * provided symbols. - * - * @param newSymbols desired DecimalFormatSymbols - * @see DecimalFormatSymbols - */ - public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { - symbols = (DecimalFormatSymbols) newSymbols.clone(); - setCurrencyForSymbols(); - expandAffixes(null); - } - - /** - * 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() { - - // Bug 4212072 Update the affix strings according to symbols in order to keep the - // affix strings up to date. [Richard/GCL] - - // 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()); - - if (symbols.getCurrencySymbol().equals(def.getCurrencySymbol()) - && symbols.getInternationalCurrencySymbol() - .equals(def.getInternationalCurrencySymbol())) { - setCurrency(Currency.getInstance(symbols.getULocale())); - } else { - setCurrency(null); - } - } - - /** - * Returns the positive prefix. - * - *

Examples: +123, $123, sFr123 - * @return the prefix - */ - public String getPositivePrefix() { - return positivePrefix; - } - - /** - * Sets the positive prefix. - * - *

Examples: +123, $123, sFr123 - * @param newValue the prefix - */ - public void setPositivePrefix(String newValue) { - positivePrefix = newValue; - posPrefixPattern = null; - } - - /** - * Returns the negative prefix. - * - *

Examples: -123, ($123) (with negative suffix), sFr-123 - * - * @return the prefix - */ - public String getNegativePrefix() { - return negativePrefix; - } - - /** - * Sets the negative prefix. - * - *

Examples: -123, ($123) (with negative suffix), sFr-123 - * @param newValue the prefix - */ - public void setNegativePrefix(String newValue) { - negativePrefix = newValue; - negPrefixPattern = null; - } - - /** - * Returns the positive suffix. - * - *

Example: 123% - * - * @return the suffix - */ - public String getPositiveSuffix() { - return positiveSuffix; - } - - /** - * Sets the positive suffix. - * - *

Example: 123% - * @param newValue the suffix - */ - public void setPositiveSuffix(String newValue) { - positiveSuffix = newValue; - posSuffixPattern = null; - } - - /** - * Returns the negative suffix. - * - *

Examples: -123%, ($123) (with positive suffixes) - * - * @return the suffix - */ - public String getNegativeSuffix() { - return negativeSuffix; - } - - /** - * Sets the positive suffix. - * - *

Examples: 123% - * @param newValue the suffix - */ - public void setNegativeSuffix(String newValue) { - negativeSuffix = newValue; - negSuffixPattern = null; - } - - /** - * 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 - */ - public int getMultiplier() { - return multiplier; - } - - /** - * 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 - */ - public void setMultiplier(int newValue) { - if (newValue == 0) { - throw new IllegalArgumentException("Bad multiplier: " + newValue); - } - multiplier = newValue; - } - - /** - * [icu] Returns the rounding increment. - * - * @return A positive rounding increment, or null if a custom rounding - * increment is not in effect. - * @see #setRoundingIncrement - * @see #getRoundingMode - * @see #setRoundingMode - */ - public java.math.BigDecimal getRoundingIncrement() { - if (roundingIncrementICU == null) - return null; - return roundingIncrementICU.toBigDecimal(); - } - - /** - * [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 - */ - public void setRoundingIncrement(java.math.BigDecimal newValue) { - if (newValue == null) { - setRoundingIncrement((BigDecimal) null); - } else { - setRoundingIncrement(new BigDecimal(newValue)); - } - } - - /** - * [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 - */ - 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(); - } - - /** - * [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 - */ - 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(); - } - - /** - * Returns the rounding mode. - * - * @return A rounding mode, between BigDecimal.ROUND_UP and - * BigDecimal.ROUND_UNNECESSARY. - * @see #setRoundingIncrement - * @see #getRoundingIncrement - * @see #setRoundingMode - * @see java.math.BigDecimal - */ - @Override - public int getRoundingMode() { - return roundingMode; - } - - /** - * 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 - */ - @Override - public void setRoundingMode(int roundingMode) { - if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) { - throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode); - } - - this.roundingMode = roundingMode; - resetActualRounding(); - } - - /** - * Returns the width to which the output of 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 - */ - public int getFormatWidth() { - return formatWidth; - } - - /** - * 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 - */ - public void setFormatWidth(int width) { - if (width < 0) { - throw new IllegalArgumentException("Illegal format width"); - } - formatWidth = width; - } - - /** - * [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 - */ - public char getPadCharacter() { - return pad; - } - - /** - * [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 - */ - public void setPadCharacter(char padChar) { - pad = padChar; - } - - /** - * [icu] Returns 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. - * - * @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 - */ - public int getPadPosition() { - return padPosition; - } - - /** - * [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 - */ - public void setPadPosition(int padPos) { - if (padPos < PAD_BEFORE_PREFIX || padPos > PAD_AFTER_SUFFIX) { - throw new IllegalArgumentException("Illegal pad position"); - } - padPosition = padPos; - } - - /** - * [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 - */ - public boolean isScientificNotation() { - return useExponentialNotation; - } - - /** - * [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 - */ - public void setScientificNotation(boolean useScientific) { - useExponentialNotation = useScientific; - } - - /** - * [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 - */ - public byte getMinimumExponentDigits() { - return minExponentDigits; - } - - /** - * [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 - */ - public void setMinimumExponentDigits(byte minExpDig) { - if (minExpDig < 1) { - throw new IllegalArgumentException("Exponent digits must be >= 1"); - } - minExponentDigits = minExpDig; - } - - /** - * [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 - */ - public boolean isExponentSignAlwaysShown() { - return exponentSignAlwaysShown; - } - - /** - * [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 - */ - public void setExponentSignAlwaysShown(boolean expSignAlways) { - exponentSignAlwaysShown = expSignAlways; - } - - /** - * 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 - */ - public int getGroupingSize() { - return groupingSize; - } - - /** - * 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 - */ - public void setGroupingSize(int newValue) { - groupingSize = (byte) newValue; - } - - /** - * [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 - */ - public int getSecondaryGroupingSize() { - return groupingSize2; - } - - /** - * [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 - */ - public void setSecondaryGroupingSize(int newValue) { - groupingSize2 = (byte) newValue; - } - - /** - * [icu] Returns the MathContext used by this format. - * - * @return desired MathContext - * @see #getMathContext - */ - public MathContext getMathContextICU() { - return mathContext; - } - - /** - * [icu] Returns the MathContext used by this format. - * - * @return desired MathContext - * @see #getMathContext - */ - 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 - } - } - - /** - * [icu] Sets the MathContext used by this format. - * - * @param newValue desired MathContext - * @see #getMathContext - */ - public void setMathContextICU(MathContext newValue) { - mathContext = newValue; - } - - /** - * [icu] Sets the MathContext used by this format. - * - * @param newValue desired MathContext - * @see #getMathContext - */ - public void setMathContext(java.math.MathContext newValue) { - mathContext = new MathContext(newValue.getPrecision(), MathContext.SCIENTIFIC, false, - (newValue.getRoundingMode()).ordinal()); - } - - /** - * Returns the behavior of the decimal separator with integers. (The decimal - * separator will always appear with decimals.)

Example: Decimal ON: 12345 -> - * 12345.; OFF: 12345 -> 12345 - */ - public boolean isDecimalSeparatorAlwaysShown() { - return decimalSeparatorAlwaysShown; - } - - /** - * 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. - */ - public void setDecimalPatternMatchRequired(boolean value) { - parseRequireDecimalPoint = value; - } - - /** - * [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 - */ - public boolean isDecimalPatternMatchRequired() { - return parseRequireDecimalPoint; - } - - - /** - * 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 - */ - public void setDecimalSeparatorAlwaysShown(boolean newValue) { - decimalSeparatorAlwaysShown = newValue; - } - - /** - * [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 - */ - public CurrencyPluralInfo getCurrencyPluralInfo() { - try { - // don't allow multiple references - return currencyPluralInfo == null ? null : - (CurrencyPluralInfo) currencyPluralInfo.clone(); - } catch (Exception foo) { - return null; // should never happen - } - } - - /** - * [icu] Sets the CurrencyPluralInfo used by this format. The format uses a copy of - * the provided information. - * - * @param newInfo desired CurrencyPluralInfo - * @see CurrencyPluralInfo - */ - public void setCurrencyPluralInfo(CurrencyPluralInfo newInfo) { - currencyPluralInfo = (CurrencyPluralInfo) newInfo.clone(); - isReadyForParsing = false; - } - - /** - * Overrides clone. - */ - @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(); // #9240 - other.currencyUsage = currencyUsage; - - // TODO: We need to figure out whether we share a single copy of DigitList by - // multiple cloned copies. format/subformat are designed to use a single - // instance, but parse/subparse implementation is not. - return other; - } catch (Exception e) { - throw new IllegalStateException(); - } - } - - /** - * Overrides equals. - */ - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - if (!super.equals(obj)) - return false; // super does class check - - DecimalFormat other = (DecimalFormat) obj; - // Add the comparison of the four new added fields ,they are posPrefixPattern, - // posSuffixPattern, negPrefixPattern, negSuffixPattern. [Richard/GCL] - // following are added to accomodate changes for currency plural format. - return currencySignCount == other.currencySignCount - && (style != NumberFormat.PLURALCURRENCYSTYLE || - equals(posPrefixPattern, other.posPrefixPattern) - && equals(posSuffixPattern, other.posSuffixPattern) - && equals(negPrefixPattern, other.negPrefixPattern) - && equals(negSuffixPattern, other.negSuffixPattern)) - && multiplier == other.multiplier - && groupingSize == other.groupingSize - && groupingSize2 == other.groupingSize2 - && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown - && useExponentialNotation == other.useExponentialNotation - && (!useExponentialNotation || minExponentDigits == other.minExponentDigits) - && useSignificantDigits == other.useSignificantDigits - && (!useSignificantDigits || minSignificantDigits == other.minSignificantDigits - && maxSignificantDigits == other.maxSignificantDigits) - && symbols.equals(other.symbols) - && Utility.objectEquals(currencyPluralInfo, other.currencyPluralInfo) - && currencyUsage.equals(other.currencyUsage); - } - - // 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)); - } - - 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(); - } - - // 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: '" + - // android.icu.impl.Utility.format1ForSource(negPrefixPattern) + "'\n"); - // buf.append("negativePrefix: '" + - // android.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. - */ - @Override - public int hashCode() { - return super.hashCode() * 37 + positivePrefix.hashCode(); - // just enough fields for a reasonable distribution - } - - /** - * Synthesizes a pattern string that represents the current state of this Format - * object. - * - * @see #applyPattern - */ - 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); - } - - /** - * Synthesizes a localized pattern string that represents the current state of this - * Format object. - * - * @see #applyPattern - */ - public String toLocalizedPattern() { - if (style == NumberFormat.PLURALCURRENCYSTYLE) { - return formatPattern; - } - return toPattern(true); - } - - /** - * 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; - - // 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(); - } - } - - /** - * 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; - } - - 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; - } - } - } - - /** - * 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. - */ - @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. - */ - 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. - */ - 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 2,000,000,000 to match ICU4C. - * - * @see NumberFormat#setMaximumIntegerDigits - */ - @Override - public void setMaximumIntegerDigits(int newValue) { - // Android changed: Allow 2 billion integer digits. - super.setMaximumIntegerDigits(Math.min(newValue, MAX_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 - */ - @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 - */ - 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 - */ - public int getMaximumSignificantDigits() { - return maxSignificantDigits; - } - - /** - * [icu] Sets the minimum number of significant digits that will be displayed. If - * 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 - */ - 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 - */ - 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 - */ - 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 - */ - 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. - */ - @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. - */ - 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 - */ - 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. - * - * @deprecated This API is ICU internal only. - * @hide original deprecated declaration - * @hide draft / provisional / internal are hidden on Android - */ - @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 - */ - @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 - */ - @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. - */ - public void setParseBigDecimal(boolean value) { - parseBigDecimal = value; - } - - /** - * Returns whether {@link #parse(String, ParsePosition)} returns BigDecimal. - * - * @return true if {@link #parse(String, ParsePosition)} returns BigDecimal. - */ - 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 - */ - 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 - */ - 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"); } - - - // Android changed: Allow 2 billion integer digits. - // Truncate the maximumIntegerDigits to MAX_INTEGER_DIGITS and - // maximumFractionDigits to DOUBLE_FRACTION_DIGITS - - if (getMaximumIntegerDigits() > MAX_INTEGER_DIGITS) { - setMaximumIntegerDigits(MAX_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. - * - *

This pattern is expanded by the method 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. - */ - 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 - */ - private boolean useSignificantDigits = false; - - /** - * The minimum number of significant digits to show. Must be >= 1 and <= - * maxSignificantDigits. Ignored unless useSignificantDigits == true. - * - * @serial - */ - private int minSignificantDigits = 1; - - /** - * The maximum number of significant digits to show. Must be >= - * minSignficantDigits. Ignored unless useSignificantDigits == true. - * - * @serial - */ - private int maxSignificantDigits = 6; - - /** - * True to force the use of exponential (i.e. scientific) notation - * when formatting numbers. - * - *

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 - * useExponentialNotation is not true. - * - *

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 useExponentialNotation is true. - * - * @serial - */ - 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 - */ - // 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 - */ - 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 - */ - 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 - */ - private int formatWidth = 0; - - /** - * The character used to pad the result of format to formatWidth, if - * padding is in effect. Default value ' '. - * - * @serial - */ - 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 - */ - 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 - * @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 - */ - 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 - */ - 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 - */ - 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 - */ - 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 - */ - 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; - // Android changed: Allow 2 billion integer digits. - // This change is necessary to stay feature-compatible in java.text.DecimalFormat which - // used to be implemented using ICU4C (which has a 2 billion integer digits limit) and - // is now implemented based on this class. - static final int MAX_INTEGER_DIGITS = 2000000000; - 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 ArrayList attributes = new ArrayList(); - - // The following are used in currency format - - // -- triple currency sign char array - // private static final char[] tripleCurrencySign = {0xA4, 0xA4, 0xA4}; - // -- triple currency sign string - // private static final String tripleCurrencyStr = new String(tripleCurrencySign); - // - // -- default currency plural pattern char array - // private static final char[] defaultCurrencyPluralPatternChar = - // {0, '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4}; - // -- default currency plural pattern string - // private static final String defaultCurrencyPluralPattern = - // new String(defaultCurrencyPluralPatternChar); - - // pattern used in this formatter - private String formatPattern = ""; - // style is only valid when decimal formatter is constructed by - // DecimalFormat(pattern, decimalFormatSymbol, style) - private int style = NumberFormat.NUMBERSTYLE; - /** - * Represents whether this is a currency format, and which currency format style. 0: - * not currency format type; 1: currency style -- symbol name, such as "$" for US - * dollar. 2: currency style -- ISO name, such as USD for US dollar. 3: currency style - * -- plural long name, such as "US Dollar" for "1.00 US Dollar", or "US Dollars" for - * "3.00 US Dollars". - */ - private int currencySignCount = CURRENCY_SIGN_COUNT_ZERO; - - /** - * For parsing purposes, we need to remember all prefix patterns and suffix patterns - * of every currency format pattern, including the pattern of the default currency - * style, ISO currency style, and plural currency style. The patterns are set through - * applyPattern. The following are used to represent the affix patterns in currency - * plural formats. - */ - private static final class AffixForCurrency { - // negative prefix pattern - private String negPrefixPatternForCurrency = null; - // negative suffix pattern - private String negSuffixPatternForCurrency = null; - // positive prefix pattern - private String posPrefixPatternForCurrency = null; - // positive suffix pattern - private String posSuffixPatternForCurrency = null; - private final int patternType; - - public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix, - String posSuffix, int type) { - negPrefixPatternForCurrency = negPrefix; - negSuffixPatternForCurrency = negSuffix; - posPrefixPatternForCurrency = posPrefix; - posSuffixPatternForCurrency = posSuffix; - patternType = type; - } - - public String getNegPrefix() { - return negPrefixPatternForCurrency; - } - - public String getNegSuffix() { - return negSuffixPatternForCurrency; - } - - public String getPosPrefix() { - return posPrefixPatternForCurrency; - } - - public String getPosSuffix() { - return posSuffixPatternForCurrency; - } - - public int getPatternType() { - return patternType; - } - } - - // Affix pattern set for currency. It is a set of AffixForCurrency, each element of - // the set saves the negative prefix, negative suffix, positive prefix, and positive - // suffix of a pattern. - private transient Set affixPatternsForCurrency = null; - - // For currency parsing. Since currency parsing needs to parse against all currency - // patterns, before the parsing, we need to set up the affix patterns for all currencies. - private transient boolean isReadyForParsing = false; - - // Information needed for DecimalFormat to format/parse currency plural. - private CurrencyPluralInfo currencyPluralInfo = null; - - /** - * Unit is an immutable class for the textual representation of a unit, in - * particular its prefix and suffix. - * - * @author rocketman - * - */ - static class Unit { - private final String prefix; - private final String suffix; - - public Unit(String prefix, String suffix) { - this.prefix = prefix; - this.suffix = suffix; - } - - public void writeSuffix(StringBuffer toAppendTo) { - toAppendTo.append(suffix); - } - - public void writePrefix(StringBuffer toAppendTo) { - toAppendTo.append(prefix); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Unit)) { - return false; - } - Unit other = (Unit) obj; - return prefix.equals(other.prefix) && suffix.equals(other.suffix); - } - @Override - public String toString() { - return prefix + "/" + suffix; - } - } - - static final Unit NULL_UNIT = new Unit("", ""); - - // Note about rounding implementation - // - // The original design intended to skip rounding operation when roundingIncrement is not - // set. However, rounding may need to occur when fractional digits exceed the width of - // fractional part of pattern. - // - // DigitList class has built-in rounding mechanism, using ROUND_HALF_EVEN. This implementation - // forces non-null roundingIncrement if the setting is other than ROUND_HALF_EVEN, otherwise, - // when rounding occurs in DigitList by pattern's fractional digits' width, the result - // does not match the rounding mode. - // - // Ideally, all rounding operation should be done in one place like ICU4C trunk does - // (ICU4C rounding implementation was rewritten recently). This is intrim implemetation - // to fix various issues. In the future, we should entire implementation of rounding - // in this class, like ICU4C did. - // - // Once we fully implement rounding logic in DigitList, then following fields and methods - // should be gone. - - private transient BigDecimal actualRoundingIncrementICU = null; - private transient java.math.BigDecimal actualRoundingIncrement = null; - - /* - * The actual rounding increment as a double. - */ - private transient double roundingDouble = 0.0; - - /* - * If the roundingDouble is the reciprocal of an integer (the most common case!), this - * is set to be that integer. Otherwise it is 0.0. - */ - private transient double roundingDoubleReciprocal = 0.0; - - /* - * Set roundingDouble, roundingDoubleReciprocal and actualRoundingIncrement - * based on rounding mode and width of fractional digits. Whenever setting affecting - * rounding mode, rounding increment and maximum width of fractional digits, then - * this method must be called. - * - * roundingIncrementICU is the field storing the custom rounding increment value, - * while actual rounding increment could be larger. - */ - private void resetActualRounding() { - if (roundingIncrementICU != null) { - BigDecimal byWidth = getMaximumFractionDigits() > 0 ? - BigDecimal.ONE.movePointLeft(getMaximumFractionDigits()) : BigDecimal.ONE; - if (roundingIncrementICU.compareTo(byWidth) >= 0) { - actualRoundingIncrementICU = roundingIncrementICU; - } else { - actualRoundingIncrementICU = byWidth.equals(BigDecimal.ONE) ? null : byWidth; - } - } else { - if (roundingMode == BigDecimal.ROUND_HALF_EVEN || isScientificNotation()) { - // This rounding fix is irrelevant if mode is ROUND_HALF_EVEN as DigitList - // does ROUND_HALF_EVEN for us. This rounding fix won't work at all for - // scientific notation. - actualRoundingIncrementICU = null; - } else { - if (getMaximumFractionDigits() > 0) { - actualRoundingIncrementICU = BigDecimal.ONE.movePointLeft(getMaximumFractionDigits()); - } else { - actualRoundingIncrementICU = BigDecimal.ONE; - } - } - } - - if (actualRoundingIncrementICU == null) { - setRoundingDouble(0.0d); - actualRoundingIncrement = null; - } else { - setRoundingDouble(actualRoundingIncrementICU.doubleValue()); - actualRoundingIncrement = actualRoundingIncrementICU.toBigDecimal(); - } - } - - static final double roundingIncrementEpsilon = 0.000000001; - - private void setRoundingDouble(double newValue) { - roundingDouble = newValue; - if (roundingDouble > 0.0d) { - double rawRoundedReciprocal = 1.0d / roundingDouble; - roundingDoubleReciprocal = Math.rint(rawRoundedReciprocal); - if (Math.abs(rawRoundedReciprocal - roundingDoubleReciprocal) > roundingIncrementEpsilon) { - roundingDoubleReciprocal = 0.0d; - } - } else { - roundingDoubleReciprocal = 0.0d; - } - } + @Deprecated + ENSURE_MINIMUM_SIGNIFICANT + } + + /** + * [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 + */ + 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 + */ + 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 + */ + 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 + */ + public static final int PAD_AFTER_SUFFIX = 3; } - -// eof -- cgit v1.2.3