# HG changeset patch # User naoto # Date 1553272296 25200 # Node ID 83deaa8f0c8ef4e611312494aef7c8955554e380 # Parent 51195881bd3a3b2cb190379a2a7656c3eb51ebf5 8220224: With CLDR provider, NumberFormat.format could not handle locale with number extension correctly. Reviewed-by: darcy diff -r 51195881bd3a -r 83deaa8f0c8e src/java.base/share/classes/java/text/CompactNumberFormat.java --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java Fri Mar 22 08:18:26 2019 -0700 +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java Fri Mar 22 09:31:36 2019 -0700 @@ -836,7 +836,8 @@ if (ch == QUOTE) { ch = pattern.charAt(index++); if (ch == MINUS_SIGN) { - ch = symbols.getMinusSign(); + sb.append(symbols.getMinusSignText()); + continue; } } sb.append(ch); @@ -859,11 +860,14 @@ if (ch == QUOTE) { ch = pattern.charAt(index++); if (ch == MINUS_SIGN) { - ch = symbols.getMinusSign(); + String minusText = symbols.getMinusSignText(); FieldPosition fp = new FieldPosition(NumberFormat.Field.SIGN); fp.setBeginIndex(stringIndex); - fp.setEndIndex(stringIndex + 1); + fp.setEndIndex(stringIndex + minusText.length()); positions.add(fp); + stringIndex += minusText.length(); + affix.append(minusText); + continue; } } stringIndex++; diff -r 51195881bd3a -r 83deaa8f0c8e src/java.base/share/classes/java/text/DecimalFormat.java --- a/src/java.base/share/classes/java/text/DecimalFormat.java Fri Mar 22 08:18:26 2019 -0700 +++ b/src/java.base/share/classes/java/text/DecimalFormat.java Fri Mar 22 09:31:36 2019 -0700 @@ -54,20 +54,20 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter; /** - * DecimalFormat is a concrete subclass of - * NumberFormat that formats decimal numbers. It has a variety of + * {@code DecimalFormat} is a concrete subclass of + * {@code 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, and Indic digits. It also * supports different kinds of numbers, including integers (123), fixed-point * numbers (123.4), scientific notation (1.23E4), percentages (12%), and * currency amounts ($123). All of these can be localized. * - *

To obtain a NumberFormat for a specific locale, including the - * default locale, call one of NumberFormat's factory methods, such - * as getInstance(). In general, do not call the - * DecimalFormat constructors directly, since the - * NumberFormat factory methods may return subclasses other than - * DecimalFormat. If you need to customize the format object, do + *

To obtain a {@code NumberFormat} for a specific locale, including the + * default locale, call one of {@code NumberFormat}'s factory methods, such + * as {@code getInstance()}. In general, do not call the + * {@code DecimalFormat} constructors directly, since the + * {@code NumberFormat} factory methods may return subclasses other than + * {@code DecimalFormat}. If you need to customize the format object, do * something like this: * *

@@ -77,16 +77,16 @@
  * }
  * 
* - *

A DecimalFormat comprises a pattern and a set of + *

A {@code DecimalFormat} comprises a pattern and a set of * symbols. The pattern may be set directly using - * applyPattern(), or indirectly using the API methods. The - * symbols are stored in a DecimalFormatSymbols object. When using - * the NumberFormat factory methods, the pattern and symbols are - * read from localized ResourceBundles. + * {@code applyPattern()}, or indirectly using the API methods. The + * symbols are stored in a {@code DecimalFormatSymbols} object. When using + * the {@code NumberFormat} factory methods, the pattern and symbols are + * read from localized {@code ResourceBundle}s. * *

Patterns

* - * DecimalFormat patterns have the following syntax: + * {@code DecimalFormat} patterns have the following syntax: *
  * Pattern:
  *         PositivePattern
@@ -123,26 +123,26 @@
  *         0 MinimumExponentopt
  * 
* - *

A DecimalFormat pattern contains a positive and negative - * subpattern, for example, "#,##0.00;(#,##0.00)". Each + *

A {@code DecimalFormat} pattern contains a positive and negative + * subpattern, for example, {@code "#,##0.00;(#,##0.00)"}. Each * subpattern has a prefix, numeric part, and suffix. The negative subpattern * is optional; if absent, then the positive subpattern prefixed with the - * localized minus sign ('-' in most locales) is used as the - * negative subpattern. That is, "0.00" alone is equivalent to - * "0.00;-0.00". If there is an explicit negative subpattern, it + * localized minus sign ({@code '-'} in most locales) is used as the + * negative subpattern. That is, {@code "0.00"} alone is equivalent to + * {@code "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 all the same as the positive - * pattern. That means that "#,##0.0#;(#)" produces precisely - * the same behavior as "#,##0.0#;(#,##0.0#)". + * pattern. That means that {@code "#,##0.0#;(#)"} produces precisely + * the same behavior as {@code "#,##0.0#;(#,##0.0#)"}. * *

The prefixes, suffixes, and various symbols used for infinity, digits, * thousands separators, decimal separators, etc. may be set to arbitrary * values, and they will appear properly during formatting. However, care must * be taken that the symbols and strings do not conflict, or parsing will be * unreliable. For example, either the positive and negative prefixes or the - * suffixes must be distinct for DecimalFormat.parse() to be able + * suffixes must be distinct for {@code DecimalFormat.parse()} to be able * to distinguish positive from negative values. (If they are identical, then - * DecimalFormat will behave as if no negative subpattern was + * {@code DecimalFormat} will behave as if no negative subpattern was * specified.) Another example is that the decimal separator and thousands * separator should be distinct characters, or parsing will be impossible. * @@ -151,8 +151,8 @@ * of digits between the grouping characters, such as 3 for 100,000,000 or 4 for * 1,0000,0000. If you supply a pattern with multiple grouping characters, the * interval between the last one and the end of the integer is the one that is - * used. So "#,##,###,####" == "######,####" == - * "##,####,####". + * used. So {@code "#,##,###,####"} == {@code "######,####"} == + * {@code "##,####,####"}. * *

Special Pattern Characters

* @@ -164,7 +164,7 @@ * *

The characters listed here are used in non-localized patterns. Localized * patterns use the corresponding characters taken from this formatter's - * DecimalFormatSymbols object instead, and these characters lose + * {@code DecimalFormatSymbols} object instead, and these characters lose * their special status. Two exceptions are the currency sign and quote, which * are not localized. * @@ -180,53 +180,53 @@ * * * - * 0 + * {@code 0} * Number * Yes * Digit * - * # + * {@code #} * Number * Yes * Digit, zero shows as absent * - * . + * {@code .} * Number * Yes * Decimal separator or monetary decimal separator * - * - + * {@code -} * Number * Yes * Minus sign * - * , + * {@code ,} * Number * Yes * Grouping separator * - * E + * {@code E} * Number * Yes * Separates mantissa and exponent in scientific notation. * Need not be quoted in prefix or suffix. * - * ; + * {@code ;} * Subpattern boundary * Yes * Separates positive and negative subpatterns * - * % + * {@code %} * Prefix or suffix * Yes * Multiply by 100 and show as percentage * - * \u2030 + * {@code \u2030} * Prefix or suffix * Yes * Multiply by 1000 and show as per mille value * - * ¤ (\u00A4) + * {@code ¤} ({@code \u00A4}) * Prefix or suffix * No * Currency sign, replaced by currency symbol. If @@ -234,13 +234,13 @@ * If present in a pattern, the monetary decimal separator * is used instead of the decimal separator. * - * ' + * {@code '} * 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". + * for example, {@code "'#'#"} formats 123 to + * {@code "#123"}. To create a single quote + * itself, use two in a row: {@code "# o''clock"}. * * * @@ -251,18 +251,18 @@ * and a power of ten, for example, 1234 can be expressed as 1.234 x 10^3. The * mantissa is often in the range 1.0 ≤ x {@literal <} 10.0, but it need not * be. - * DecimalFormat can be instructed to format and parse scientific + * {@code DecimalFormat} can be instructed to format and parse scientific * notation only via a pattern; there is currently no factory method * that creates a scientific notation format. 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". + * scientific notation. Example: {@code "0.###E0"} formats the number + * 1234 as {@code "1.234E3"}. * *

* *
  • The number of significant digits in the mantissa is the sum of the * minimum integer and maximum fraction digits, and is * unaffected by the maximum integer digits. For example, 12345 formatted with - * "##0.##E0" is "12.3E3". To show all digits, set + * {@code "##0.##E0"} is {@code "12.3E3"}. To show all digits, set * the significant digits count to zero. The number of significant digits * does not affect parsing. * @@ -294,38 +294,38 @@ * *

    Rounding

    * - * DecimalFormat provides rounding modes defined in + * {@code DecimalFormat} provides rounding modes defined in * {@link java.math.RoundingMode} for formatting. By default, it uses * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. * *

    Digits

    * - * For formatting, DecimalFormat uses the ten consecutive + * For formatting, {@code DecimalFormat} uses the ten consecutive * characters starting with the localized zero digit defined in the - * DecimalFormatSymbols object as digits. For parsing, these + * {@code DecimalFormatSymbols} object as digits. For parsing, these * digits as well as all Unicode decimal digits, as defined by * {@link Character#digit Character.digit}, are recognized. * *

    Special Values

    * - *

    NaN is formatted as a string, which typically has a single character - * \uFFFD. This string is determined by the - * DecimalFormatSymbols object. This is the only value for which + *

    {@code NaN} is formatted as a string, which typically has a single character + * {@code \uFFFD}. This string is determined by the + * {@code DecimalFormatSymbols} object. This is the only value for which * the prefixes and suffixes are not used. * *

    Infinity is formatted as a string, which typically has a single character - * \u221E, with the positive or negative prefixes and suffixes + * {@code \u221E}, with the positive or negative prefixes and suffixes * applied. The infinity string is determined by the - * DecimalFormatSymbols object. + * {@code DecimalFormatSymbols} object. * - *

    Negative zero ("-0") parses to + *

    Negative zero ({@code "-0"}) parses to *

    * *

    Synchronization

    @@ -425,7 +425,7 @@ * locale. * * @param pattern a non-localized pattern string. - * @exception NullPointerException if pattern is null + * @exception NullPointerException if {@code pattern} is null * @exception IllegalArgumentException if the given pattern is invalid. * @see java.text.NumberFormat#getInstance * @see java.text.NumberFormat#getNumberInstance @@ -475,7 +475,7 @@ *

    * This implementation uses the maximum precision permitted. * @param number the number to format - * @param toAppendTo the StringBuffer to which the formatted + * @param toAppendTo the {@code StringBuffer} to which the formatted * text is to be appended * @param pos keeps track on the position of the field within the * returned string. For example, for formatting a number @@ -485,11 +485,11 @@ * and end index of {@code fieldPosition} will be set * to 0 and 9, respectively for the output string * {@code 1,234,567.89}. - * @return the value passed in as toAppendTo - * @exception IllegalArgumentException if number is - * null or not an instance of Number. - * @exception NullPointerException if toAppendTo or - * pos is null + * @return the value passed in as {@code toAppendTo} + * @exception IllegalArgumentException if {@code number} is + * null or not an instance of {@code Number}. + * @exception NullPointerException if {@code toAppendTo} or + * {@code pos} is null * @exception ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition @@ -914,13 +914,13 @@ } /** - * Formats an Object producing an AttributedCharacterIterator. - * You can use the returned AttributedCharacterIterator + * Formats an Object producing an {@code AttributedCharacterIterator}. + * You can use the returned {@code AttributedCharacterIterator} * to build the resulting String, as well as to determine information * about the resulting String. *

    * Each attribute key of the AttributedCharacterIterator will be of type - * NumberFormat.Field, with the attribute value being the + * {@code NumberFormat.Field}, with the attribute value being the * same as the attribute key. * * @exception NullPointerException if obj is null. @@ -1916,7 +1916,7 @@ if (negativeExponent) { exponent = -exponent; fieldStart = result.length(); - result.append(symbols.getMinusSign()); + result.append(symbols.getMinusSignText()); delegate.formatted(Field.EXPONENT_SIGN, Field.EXPONENT_SIGN, fieldStart, result.length(), result); } @@ -2042,17 +2042,17 @@ } /** - * Appends the String string to result. - * delegate is notified of all the - * FieldPositions in positions. + * Appends the String {@code string} to {@code result}. + * {@code delegate} is notified of all the + * {@code FieldPosition}s in {@code positions}. *

    - * If one of the FieldPositions in positions - * identifies a SIGN attribute, it is mapped to - * signAttribute. This is used - * to map the SIGN attribute to the EXPONENT + * If one of the {@code FieldPosition}s in {@code positions} + * identifies a {@code SIGN} attribute, it is mapped to + * {@code signAttribute}. This is used + * to map the {@code SIGN} attribute to the {@code EXPONENT} * attribute as necessary. *

    - * This is used by subformat to add the prefix/suffix. + * This is used by {@code subformat} to add the prefix/suffix. */ private void append(StringBuffer result, String string, FieldDelegate delegate, @@ -2078,60 +2078,60 @@ } /** - * Parses text from a string to produce a Number. + * Parses text from a string to produce a {@code Number}. *

    * The method attempts to parse text starting at the index given by - * pos. - * If parsing succeeds, then the index of pos is updated + * {@code pos}. + * If parsing succeeds, then the index of {@code pos} is updated * to the index after the last character used (parsing does not necessarily * use all characters up to the end of the string), and the parsed - * number is returned. The updated pos can be used to + * number is returned. The updated {@code pos} can be used to * indicate the starting point for the next call to this method. - * If an error occurs, then the index of pos is not - * changed, the error index of pos is set to the index of + * If an error occurs, then the index of {@code pos} is not + * changed, the error index of {@code pos} is set to the index of * the character where the error occurred, and null is returned. *

    * The subclass returned depends on the value of {@link #isParseBigDecimal} * as well as on the string being parsed. *

    *

    - * DecimalFormat parses all Unicode characters that represent - * decimal digits, as defined by Character.digit(). In - * addition, DecimalFormat also recognizes as digits the ten + * {@code DecimalFormat} parses all Unicode characters that represent + * decimal digits, as defined by {@code Character.digit()}. In + * addition, {@code DecimalFormat} also recognizes as digits the ten * consecutive characters starting with the localized zero digit defined in - * the DecimalFormatSymbols object. + * the {@code DecimalFormatSymbols} object. * * @param text the string to be parsed - * @param pos A ParsePosition object with index and error + * @param pos A {@code ParsePosition} object with index and error * index information as described above. - * @return the parsed value, or null if the parse fails - * @exception NullPointerException if text or - * pos is null. + * @return the parsed value, or {@code null} if the parse fails + * @exception NullPointerException if {@code text} or + * {@code pos} is null. */ @Override public Number parse(String text, ParsePosition pos) { @@ -2475,7 +2475,7 @@ boolean[] stat = new boolean[STATUS_LENGTH]; DigitList exponentDigits = new DigitList(); - if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) && + if (subparse(text, pos, "", symbols.getMinusSignText(), exponentDigits, true, stat) && exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { position = pos.index; // Advance past the exponent exponent = (int)exponentDigits.getLong(); @@ -2573,7 +2573,7 @@ /** * Returns the FieldPositions of the fields in the prefix used for * positive numbers. This is not used if the user has explicitly set - * a positive prefix via setPositivePrefix. This is + * a positive prefix via {@code setPositivePrefix}. This is * lazily created. * * @return FieldPositions in positive prefix @@ -2614,7 +2614,7 @@ /** * Returns the FieldPositions of the fields in the prefix used for * negative numbers. This is not used if the user has explicitly set - * a negative prefix via setNegativePrefix. This is + * a negative prefix via {@code setNegativePrefix}. This is * lazily created. * * @return FieldPositions in positive prefix @@ -2655,7 +2655,7 @@ /** * Returns the FieldPositions of the fields in the suffix used for * positive numbers. This is not used if the user has explicitly set - * a positive suffix via setPositiveSuffix. This is + * a positive suffix via {@code setPositiveSuffix}. This is * lazily created. * * @return FieldPositions in positive prefix @@ -2696,7 +2696,7 @@ /** * Returns the FieldPositions of the fields in the suffix used for * negative numbers. This is not used if the user has explicitly set - * a negative suffix via setNegativeSuffix. This is + * a negative suffix via {@code setNegativeSuffix}. This is * lazily created. * * @return FieldPositions in positive prefix @@ -2811,7 +2811,7 @@ /** * Returns whether the {@link #parse(java.lang.String, java.text.ParsePosition)} - * method returns BigDecimal. The default value is false. + * method returns {@code BigDecimal}. The default value is false. * * @return {@code true} if the parse method returns BigDecimal; * {@code false} otherwise @@ -2824,7 +2824,7 @@ /** * Sets whether the {@link #parse(java.lang.String, java.text.ParsePosition)} - * method returns BigDecimal. + * method returns {@code BigDecimal}. * * @param newValue {@code true} if the parse method returns BigDecimal; * {@code false} otherwise @@ -2991,14 +2991,14 @@ } continue; case PATTERN_PERCENT: - c = symbols.getPercent(); - break; + buffer.append(symbols.getPercentText()); + continue; case PATTERN_PER_MILLE: - c = symbols.getPerMill(); - break; + buffer.append(symbols.getPerMillText()); + continue; case PATTERN_MINUS: - c = symbols.getMinusSign(); - break; + buffer.append(symbols.getMinusSignText()); + continue; } } buffer.append(c); @@ -3027,12 +3027,11 @@ for (int i=0; i(2); - } - FieldPosition fp = new FieldPosition(Field.CURRENCY); - fp.setBeginIndex(stringIndex); - fp.setEndIndex(stringIndex + string.length()); - positions.add(fp); - stringIndex += string.length(); - } - continue; + fieldID = Field.CURRENCY; + break; case PATTERN_PERCENT: - c = symbols.getPercent(); - field = -1; + string = symbols.getPercentText(); fieldID = Field.PERCENT; break; case PATTERN_PER_MILLE: - c = symbols.getPerMill(); - field = -1; + string = symbols.getPerMillText(); fieldID = Field.PERMILLE; break; case PATTERN_MINUS: - c = symbols.getMinusSign(); - field = -1; + string = symbols.getMinusSignText(); fieldID = Field.SIGN; break; } - if (fieldID != null) { + + if (fieldID != null && !string.isEmpty()) { if (positions == null) { positions = new ArrayList<>(2); } - FieldPosition fp = new FieldPosition(fieldID, field); + FieldPosition fp = new FieldPosition(fieldID); fp.setBeginIndex(stringIndex); - fp.setEndIndex(stringIndex + 1); + fp.setEndIndex(stringIndex + string.length()); positions.add(fp); + stringIndex += string.length(); + continue; } } stringIndex++; @@ -3129,14 +3119,14 @@ } else if (localized) { switch (c) { case PATTERN_PERCENT: - c = symbols.getPercent(); - break; + buffer.append(symbols.getPercentText()); + continue; case PATTERN_PER_MILLE: - c = symbols.getPerMill(); - break; + buffer.append(symbols.getPerMillText()); + continue; case PATTERN_MINUS: - c = symbols.getMinusSign(); - break; + buffer.append(symbols.getMinusSignText()); + continue; } } buffer.append(c); @@ -3155,11 +3145,11 @@ needQuote = affix.indexOf(symbols.getZeroDigit()) >= 0 || affix.indexOf(symbols.getGroupingSeparator()) >= 0 || affix.indexOf(symbols.getDecimalSeparator()) >= 0 - || affix.indexOf(symbols.getPercent()) >= 0 - || affix.indexOf(symbols.getPerMill()) >= 0 + || affix.indexOf(symbols.getPercentText()) >= 0 + || affix.indexOf(symbols.getPerMillText()) >= 0 || affix.indexOf(symbols.getDigit()) >= 0 || affix.indexOf(symbols.getPatternSeparator()) >= 0 - || affix.indexOf(symbols.getMinusSign()) >= 0 + || affix.indexOf(symbols.getMinusSignText()) >= 0 || affix.indexOf(CURRENCY_SIGN) >= 0; } else { needQuote = affix.indexOf(PATTERN_ZERO_DIGIT) >= 0 @@ -3235,7 +3225,7 @@ if ((negPrefixPattern != null && posPrefixPattern != null && negPrefixPattern.equals("'-" + posPrefixPattern)) || (negPrefixPattern == posPrefixPattern && // n == p == null - negativePrefix.equals(symbols.getMinusSign() + positivePrefix))) + negativePrefix.equals(symbols.getMinusSignText() + positivePrefix))) break; } result.append(localized ? symbols.getPatternSeparator() : @@ -3255,16 +3245,16 @@ * 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 + *

    Example {@code "#,#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 + *

    Example: {@code "#,#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. * * @param pattern a new pattern - * @exception NullPointerException if pattern is null + * @exception NullPointerException if {@code pattern} is null * @exception IllegalArgumentException if the given pattern is invalid. */ public void applyPattern(String pattern) { @@ -3282,16 +3272,16 @@ * 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 + *

    Example {@code "#,#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 + *

    Example: {@code "#,#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. * * @param pattern a new pattern - * @exception NullPointerException if pattern is null + * @exception NullPointerException if {@code pattern} is null * @exception IllegalArgumentException if the given pattern is invalid. */ public void applyLocalizedPattern(String pattern) { @@ -3309,7 +3299,7 @@ char perMill = PATTERN_PER_MILLE; char digit = PATTERN_DIGIT; char separator = PATTERN_SEPARATOR; - String exponent = PATTERN_EXPONENT; + String exponent = PATTERN_EXPONENT; char minus = PATTERN_MINUS; if (localized) { zeroDigit = symbols.getZeroDigit(); @@ -3635,8 +3625,8 @@ /** * Sets the maximum number of digits allowed in the integer portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of newValue and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of {@code newValue} and * 309 is used. Negative input values are replaced with 0. * @see NumberFormat#setMaximumIntegerDigits */ @@ -3656,8 +3646,8 @@ /** * Sets the minimum number of digits allowed in the integer portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of newValue and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of {@code newValue} and * 309 is used. Negative input values are replaced with 0. * @see NumberFormat#setMinimumIntegerDigits */ @@ -3677,8 +3667,8 @@ /** * Sets the maximum number of digits allowed in the fraction portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of newValue and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of {@code newValue} and * 340 is used. Negative input values are replaced with 0. * @see NumberFormat#setMaximumFractionDigits */ @@ -3698,8 +3688,8 @@ /** * Sets the minimum number of digits allowed in the fraction portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of newValue and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of {@code newValue} and * 340 is used. Negative input values are replaced with 0. * @see NumberFormat#setMinimumFractionDigits */ @@ -3719,8 +3709,8 @@ /** * Gets the maximum number of digits allowed in the integer portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of the return value and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of the return value and * 309 is used. * @see #setMaximumIntegerDigits */ @@ -3732,8 +3722,8 @@ /** * Gets the minimum number of digits allowed in the integer portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of the return value and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of the return value and * 309 is used. * @see #setMinimumIntegerDigits */ @@ -3745,8 +3735,8 @@ /** * Gets the maximum number of digits allowed in the fraction portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of the return value and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of the return value and * 340 is used. * @see #setMaximumFractionDigits */ @@ -3758,8 +3748,8 @@ /** * Gets the minimum number of digits allowed in the fraction portion of a * number. - * For formatting numbers other than BigInteger and - * BigDecimal objects, the lower of the return value and + * For formatting numbers other than {@code BigInteger} and + * {@code BigDecimal} objects, the lower of the return value and * 340 is used. * @see #setMinimumFractionDigits */ @@ -3775,7 +3765,7 @@ * {@link DecimalFormatSymbols#getCurrency DecimalFormatSymbols.getCurrency} * on this number format's symbols. * - * @return the currency used by this decimal format, or null + * @return the currency used by this decimal format, or {@code null} * @since 1.4 */ @Override @@ -3792,7 +3782,7 @@ * on this number format's symbols. * * @param currency the new currency to be used by this decimal format - * @exception NullPointerException if currency is null + * @exception NullPointerException if {@code currency} is null * @since 1.4 */ @Override @@ -3809,7 +3799,7 @@ /** * Gets the {@link java.math.RoundingMode} used in this DecimalFormat. * - * @return The RoundingMode used for this DecimalFormat. + * @return The {@code RoundingMode} used for this DecimalFormat. * @see #setRoundingMode(RoundingMode) * @since 1.6 */ @@ -3821,9 +3811,9 @@ /** * Sets the {@link java.math.RoundingMode} used in this DecimalFormat. * - * @param roundingMode The RoundingMode to be used + * @param roundingMode The {@code RoundingMode} to be used * @see #getRoundingMode() - * @exception NullPointerException if roundingMode is null. + * @exception NullPointerException if {@code roundingMode} is null. * @since 1.6 */ @Override @@ -3845,38 +3835,38 @@ *

  • * Verify that the superclass's digit count fields correctly reflect * the limits imposed on formatting numbers other than - * BigInteger and BigDecimal objects. These + * {@code BigInteger} and {@code BigDecimal} objects. These * limits are stored in the superclass for serialization compatibility - * with older versions, while the limits for BigInteger and - * BigDecimal objects are kept in this class. + * with older versions, while the limits for {@code BigInteger} and + * {@code BigDecimal} objects are kept in this class. * If, in the superclass, the minimum or maximum integer digit count is - * larger than DOUBLE_INTEGER_DIGITS or if the minimum or + * larger than {@code DOUBLE_INTEGER_DIGITS} or if the minimum or * maximum fraction digit count is larger than - * DOUBLE_FRACTION_DIGITS, then the stream data is invalid - * and this method throws an InvalidObjectException. + * {@code DOUBLE_FRACTION_DIGITS}, then the stream data is invalid + * and this method throws an {@code InvalidObjectException}. *
  • - * If serialVersionOnStream is less than 4, initialize - * roundingMode to {@link java.math.RoundingMode#HALF_EVEN + * If {@code serialVersionOnStream} is less than 4, initialize + * {@code roundingMode} to {@link java.math.RoundingMode#HALF_EVEN * RoundingMode.HALF_EVEN}. This field is new with version 4. *
  • - * If serialVersionOnStream is less than 3, then call + * If {@code serialVersionOnStream} is less than 3, then call * the setters for the minimum and maximum integer and fraction digits with * the values of the corresponding superclass getters to initialize the * fields in this class. The fields in this class are new with version 3. *
  • - * If serialVersionOnStream is less than 1, indicating that + * If {@code serialVersionOnStream} is less than 1, indicating that * the stream was written by JDK 1.1, initialize - * useExponentialNotation + * {@code useExponentialNotation} * to false, since it was not present in JDK 1.1. *
  • - * Set serialVersionOnStream to the maximum allowed value so + * Set {@code serialVersionOnStream} to the maximum allowed value so * that default serialization will work properly if this object is streamed * out again. * * *

    Stream versions older than 2 will not have the affix pattern variables - * posPrefixPattern etc. As a result, they will be initialized - * to null, which means the affix strings will be taken as + * {@code posPrefixPattern} etc. As a result, they will be initialized + * to {@code null}, which means the affix strings will be taken as * literal values. This is exactly what we want, since that corresponds to * the pre-version-2 behavior. */ @@ -3960,14 +3950,14 @@ /** * The prefix pattern for non-negative numbers. This variable corresponds - * to positivePrefix. + * to {@code 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 + *

    This pattern is expanded by the method {@code expandAffix()} to + * {@code positivePrefix} to update the latter to reflect changes in + * {@code symbols}. If this variable is {@code null} then + * {@code positivePrefix} is taken as a literal value that does not + * change when {@code symbols} changes. This variable is always + * {@code null} for {@code DecimalFormat} objects older than * stream version 2 restored from stream. * * @serial @@ -3977,8 +3967,8 @@ /** * The suffix pattern for non-negative numbers. This variable corresponds - * to positiveSuffix. This variable is analogous to - * posPrefixPattern; see that variable for further + * to {@code positiveSuffix}. This variable is analogous to + * {@code posPrefixPattern}; see that variable for further * documentation. * * @serial @@ -3988,8 +3978,8 @@ /** * The prefix pattern for negative numbers. This variable corresponds - * to negativePrefix. This variable is analogous to - * posPrefixPattern; see that variable for further + * to {@code negativePrefix}. This variable is analogous to + * {@code posPrefixPattern}; see that variable for further * documentation. * * @serial @@ -3999,8 +3989,8 @@ /** * The suffix pattern for negative numbers. This variable corresponds - * to negativeSuffix. This variable is analogous to - * posPrefixPattern; see that variable for further + * to {@code negativeSuffix}. This variable is analogous to + * {@code posPrefixPattern}; see that variable for further * documentation. * * @serial @@ -4019,7 +4009,7 @@ /** * The number of digits between grouping separators in the integer * portion of a number. Must be greater than 0 if - * NumberFormat.groupingUsed is true. + * {@code NumberFormat.groupingUsed} is true. * * @serial * @see #getGroupingSize @@ -4053,7 +4043,7 @@ private transient boolean isCurrencyFormat = false; /** - * The DecimalFormatSymbols object used by this format. + * The {@code DecimalFormatSymbols} object used by this format. * It contains the symbols used to format numbers, e.g. the grouping separator, * decimal separator, and so on. * @@ -4074,28 +4064,28 @@ /** * FieldPositions describing the positive prefix String. This is - * lazily created. Use getPositivePrefixFieldPositions + * lazily created. Use {@code getPositivePrefixFieldPositions} * when needed. */ private transient FieldPosition[] positivePrefixFieldPositions; /** * FieldPositions describing the positive suffix String. This is - * lazily created. Use getPositiveSuffixFieldPositions + * lazily created. Use {@code getPositiveSuffixFieldPositions} * when needed. */ private transient FieldPosition[] positiveSuffixFieldPositions; /** * FieldPositions describing the negative prefix String. This is - * lazily created. Use getNegativePrefixFieldPositions + * lazily created. Use {@code getNegativePrefixFieldPositions} * when needed. */ private transient FieldPosition[] negativePrefixFieldPositions; /** * FieldPositions describing the negative suffix String. This is - * lazily created. Use getNegativeSuffixFieldPositions + * lazily created. Use {@code getNegativeSuffixFieldPositions} * when needed. */ private transient FieldPosition[] negativeSuffixFieldPositions; @@ -4103,7 +4093,7 @@ /** * 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. + * {@code useExponentialNotation} is not true. * * @serial * @since 1.2 @@ -4112,9 +4102,9 @@ /** * The maximum number of digits allowed in the integer portion of a - * BigInteger or BigDecimal number. - * maximumIntegerDigits must be greater than or equal to - * minimumIntegerDigits. + * {@code BigInteger} or {@code BigDecimal} number. + * {@code maximumIntegerDigits} must be greater than or equal to + * {@code minimumIntegerDigits}. * * @serial * @see #getMaximumIntegerDigits @@ -4124,9 +4114,9 @@ /** * The minimum number of digits allowed in the integer portion of a - * BigInteger or BigDecimal number. - * minimumIntegerDigits must be less than or equal to - * maximumIntegerDigits. + * {@code BigInteger} or {@code BigDecimal} number. + * {@code minimumIntegerDigits} must be less than or equal to + * {@code maximumIntegerDigits}. * * @serial * @see #getMinimumIntegerDigits @@ -4136,9 +4126,9 @@ /** * The maximum number of digits allowed in the fractional portion of a - * BigInteger or BigDecimal number. - * maximumFractionDigits must be greater than or equal to - * minimumFractionDigits. + * {@code BigInteger} or {@code BigDecimal} number. + * {@code maximumFractionDigits} must be greater than or equal to + * {@code minimumFractionDigits}. * * @serial * @see #getMaximumFractionDigits @@ -4148,9 +4138,9 @@ /** * The minimum number of digits allowed in the fractional portion of a - * BigInteger or BigDecimal number. - * minimumFractionDigits must be less than or equal to - * maximumFractionDigits. + * {@code BigInteger} or {@code BigDecimal} number. + * {@code minimumFractionDigits} must be less than or equal to + * {@code maximumFractionDigits}. * * @serial * @see #getMinimumFractionDigits @@ -4247,19 +4237,19 @@ *

    * @since 1.2 * @serial diff -r 51195881bd3a -r 83deaa8f0c8e src/java.base/share/classes/java/text/DecimalFormatSymbols.java --- a/src/java.base/share/classes/java/text/DecimalFormatSymbols.java Fri Mar 22 08:18:26 2019 -0700 +++ b/src/java.base/share/classes/java/text/DecimalFormatSymbols.java Fri Mar 22 09:31:36 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,12 +38,14 @@ package java.text; +import java.io.InvalidObjectException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.text.spi.DecimalFormatSymbolsProvider; import java.util.Currency; import java.util.Locale; +import java.util.Objects; import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.LocaleServiceProviderPool; @@ -51,11 +53,11 @@ /** * This class represents the set of symbols (such as the decimal separator, - * the grouping separator, and so on) needed by DecimalFormat - * to format numbers. DecimalFormat creates for itself an instance of - * DecimalFormatSymbols from its locale data. If you need to change any - * of these symbols, you can get the DecimalFormatSymbols object from - * your DecimalFormat and modify it. + * the grouping separator, and so on) needed by {@code DecimalFormat} + * to format numbers. {@code DecimalFormat} creates for itself an instance of + * {@code DecimalFormatSymbols} from its locale data. If you need to change any + * of these symbols, you can get the {@code DecimalFormatSymbols} object from + * your {@code DecimalFormat} and modify it. * *

    If the locale contains "rg" (region override) * Unicode extension, @@ -107,7 +109,7 @@ * instead of the Latin numbering system. * * @param locale the desired locale - * @exception NullPointerException if locale is null + * @exception NullPointerException if {@code locale} is null */ public DecimalFormatSymbols( Locale locale ) { initialize( locale ); @@ -115,16 +117,16 @@ /** * Returns an array of all locales for which the - * getInstance methods of this class can return + * {@code getInstance} methods of this class can return * localized instances. * The returned array represents the union of locales supported by the Java * runtime and by installed * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} - * implementations. It must contain at least a Locale + * implementations. It must contain at least a {@code Locale} * instance equal to {@link java.util.Locale#US Locale.US}. * * @return an array of locales for which localized - * DecimalFormatSymbols instances are available. + * {@code DecimalFormatSymbols} instances are available. * @since 1.6 */ public static Locale[] getAvailableLocales() { @@ -134,8 +136,8 @@ } /** - * Gets the DecimalFormatSymbols instance for the default - * locale. This method provides access to DecimalFormatSymbols + * Gets the {@code DecimalFormatSymbols} instance for the default + * locale. This method provides access to {@code DecimalFormatSymbols} * instances for locales supported by the Java runtime itself as well * as for those supported by installed * {@link java.text.spi.DecimalFormatSymbolsProvider @@ -145,7 +147,7 @@ * getInstance(Locale.getDefault(Locale.Category.FORMAT))}. * @see java.util.Locale#getDefault(java.util.Locale.Category) * @see java.util.Locale.Category#FORMAT - * @return a DecimalFormatSymbols instance. + * @return a {@code DecimalFormatSymbols} instance. * @since 1.6 */ public static final DecimalFormatSymbols getInstance() { @@ -153,8 +155,8 @@ } /** - * Gets the DecimalFormatSymbols instance for the specified - * locale. This method provides access to DecimalFormatSymbols + * Gets the {@code DecimalFormatSymbols} instance for the specified + * locale. This method provides access to {@code DecimalFormatSymbols} * instances for locales supported by the Java runtime itself as well * as for those supported by installed * {@link java.text.spi.DecimalFormatSymbolsProvider @@ -169,8 +171,8 @@ * instead of the Latin numbering system. * * @param locale the desired locale. - * @return a DecimalFormatSymbols instance. - * @exception NullPointerException if locale is null + * @return a {@code DecimalFormatSymbols} instance. + * @exception NullPointerException if {@code locale} is null * @since 1.6 */ public static final DecimalFormatSymbols getInstance(Locale locale) { @@ -255,6 +257,41 @@ */ public void setPerMill(char perMill) { this.perMill = perMill; + this.perMillText = Character.toString(perMill); + } + + /** + * Gets the string used for per mille sign. Different for Arabic, etc. + * + * @return the string used for per mille sign + * @since 13 + */ + String getPerMillText() { + return perMillText; + } + + /** + * Sets the string used for per mille sign. Different for Arabic, etc. + * + * Setting the {@code perMillText} affects the return value of + * {@link #getPerMill()}, in which the first non-format character of + * {@code perMillText} is returned. + * + * @param perMillText the string used for per mille sign + * @throws NullPointerException if {@code perMillText} is null + * @throws IllegalArgumentException if {@code perMillText} is an empty string + * @see #getPerMill() + * @see #getPerMillText() + * @since 13 + */ + void setPerMillText(String perMillText) { + Objects.requireNonNull(perMillText); + if (perMillText.isEmpty()) { + throw new IllegalArgumentException("Empty argument string"); + } + + this.perMillText = perMillText; + this.perMill = findNonFormatChar(perMillText, '\u2030'); } /** @@ -273,6 +310,41 @@ */ public void setPercent(char percent) { this.percent = percent; + this.percentText = Character.toString(percent); + } + + /** + * Gets the string used for percent sign. Different for Arabic, etc. + * + * @return the string used for percent sign + * @since 13 + */ + String getPercentText() { + return percentText; + } + + /** + * Sets the string used for percent sign. Different for Arabic, etc. + * + * Setting the {@code percentText} affects the return value of + * {@link #getPercent()}, in which the first non-format character of + * {@code percentText} is returned. + * + * @param percentText the string used for percent sign + * @throws NullPointerException if {@code percentText} is null + * @throws IllegalArgumentException if {@code percentText} is an empty string + * @see #getPercent() + * @see #getPercentText() + * @since 13 + */ + void setPercentText(String percentText) { + Objects.requireNonNull(percentText); + if (percentText.isEmpty()) { + throw new IllegalArgumentException("Empty argument string"); + } + + this.percentText = percentText; + this.percent = findNonFormatChar(percentText, '%'); } /** @@ -373,6 +445,46 @@ */ public void setMinusSign(char minusSign) { this.minusSign = minusSign; + this.minusSignText = Character.toString(minusSign); + } + + /** + * Gets the string used to represent minus sign. If no explicit + * negative format is specified, one is formed by prefixing + * minusSignText to the positive format. + * + * @return the string representing minus sign + * @since 13 + */ + String getMinusSignText() { + return minusSignText; + } + + /** + * Sets the string used to represent minus sign. If no explicit + * negative format is specified, one is formed by prefixing + * minusSignText to the positive format. + * + * Setting the {@code minusSignText} affects the return value of + * {@link #getMinusSign()}, in which the first non-format character of + * {@code minusSignText} is returned. + * + * @param minusSignText the character representing minus sign + * @throws NullPointerException if {@code minusSignText} is null + * @throws IllegalArgumentException if {@code minusSignText} is an + * empty string + * @see #getMinusSign() + * @see #getMinusSignText() + * @since 13 + */ + void setMinusSignText(String minusSignText) { + Objects.requireNonNull(minusSignText); + if (minusSignText.isEmpty()) { + throw new IllegalArgumentException("Empty argument string"); + } + + this.minusSignText = minusSignText; + this.minusSign = findNonFormatChar(minusSignText, '-'); } /** @@ -464,7 +576,7 @@ * symbol attribute to the currency's ISO 4217 currency code. * * @param currency the new currency to be used - * @exception NullPointerException if currency is null + * @exception NullPointerException if {@code currency} is null * @since 1.4 * @see #setCurrencySymbol * @see #setInternationalCurrencySymbol @@ -540,7 +652,7 @@ * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. * * @param exp the exponent separator string - * @exception NullPointerException if exp is null + * @exception NullPointerException if {@code exp} is null * @see #getExponentSeparator() * @since 1.6 */ @@ -583,9 +695,12 @@ groupingSeparator == other.groupingSeparator && decimalSeparator == other.decimalSeparator && percent == other.percent && + percentText.equals(other.percentText) && perMill == other.perMill && + perMillText.equals(other.perMillText) && digit == other.digit && minusSign == other.minusSign && + minusSignText.equals(other.minusSignText) && patternSeparator == other.patternSeparator && infinity.equals(other.infinity) && NaN.equals(other.NaN) && @@ -631,13 +746,16 @@ decimalSeparator = numberElements[0].charAt(0); groupingSeparator = numberElements[1].charAt(0); patternSeparator = numberElements[2].charAt(0); - percent = numberElements[3].charAt(0); + percentText = numberElements[3]; + percent = findNonFormatChar(percentText, '%'); zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc. digit = numberElements[5].charAt(0); - minusSign = numberElements[6].charAt(0); + minusSignText = numberElements[6]; + minusSign = findNonFormatChar(minusSignText, '-'); exponential = numberElements[7].charAt(0); exponentialSeparator = numberElements[7]; //string representation new since 1.6 - perMill = numberElements[8].charAt(0); + perMillText = numberElements[8]; + perMill = findNonFormatChar(perMillText, '\u2030'); infinity = numberElements[9]; NaN = numberElements[10]; @@ -652,6 +770,16 @@ } /** + * Obtains non-format single character from String + */ + private char findNonFormatChar(String src, char defChar) { + return (char)src.chars() + .filter(c -> Character.getType(c) != Character.FORMAT) + .findFirst() + .orElse(defChar); + } + + /** * Lazy initialization for currency related fields */ private void initializeCurrency(Locale locale) { @@ -704,18 +832,24 @@ /** * Reads the default serializable fields, provides default values for objects * in older serial versions, and initializes non-serializable fields. - * If serialVersionOnStream - * is less than 1, initializes monetarySeparator to be - * the same as decimalSeparator and exponential + * If {@code serialVersionOnStream} + * is less than 1, initializes {@code monetarySeparator} to be + * the same as {@code decimalSeparator} and {@code exponential} * to be 'E'. - * If serialVersionOnStream is less than 2, - * initializes localeto the root locale, and initializes - * If serialVersionOnStream is less than 3, it initializes - * exponentialSeparator using exponential. - * Sets serialVersionOnStream back to the maximum allowed value so that + * If {@code serialVersionOnStream} is less than 2, + * initializes {@code locale}to the root locale, and initializes + * If {@code serialVersionOnStream} is less than 3, it initializes + * {@code exponentialSeparator} using {@code exponential}. + * If {@code serialVersionOnStream} is less than 4, it initializes + * {@code perMillText}, {@code percentText}, and + * {@code minusSignText} using {@code perMill}, {@code percent}, and + * {@code minusSign} respectively. + * Sets {@code serialVersionOnStream} back to the maximum allowed value so that * default serialization will work properly if this object is streamed out again. * Initializes the currency from the intlCurrencySymbol field. * + * @throws InvalidObjectException if {@code char} and {@code String} + * representations of either percent, per mille, and/or minus sign disagree. * @since 1.1.6 */ private void readObject(ObjectInputStream stream) @@ -735,6 +869,23 @@ // didn't have exponentialSeparator. Create one using exponential exponentialSeparator = Character.toString(exponential); } + if (serialVersionOnStream < 4) { + // didn't have perMillText, percentText, and minusSignText. + // Create one using corresponding char variations. + perMillText = Character.toString(perMill); + percentText = Character.toString(percent); + minusSignText = Character.toString(minusSign); + } else { + // Check whether char and text fields agree + if (findNonFormatChar(perMillText, '\uFFFF') != perMill || + findNonFormatChar(percentText, '\uFFFF') != percent || + findNonFormatChar(minusSignText, '\uFFFF') != minusSign) { + throw new InvalidObjectException( + "'char' and 'String' representations of either percent, " + + "per mille, and/or minus sign disagree."); + } + } + serialVersionOnStream = currentSerialVersion; if (intlCurrencySymbol != null) { @@ -862,8 +1013,8 @@ * The string used to separate the mantissa from the exponent. * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. *

    - * If both exponential and exponentialSeparator - * exist, this exponentialSeparator has the precedence. + * If both {@code exponential} and {@code exponentialSeparator} + * exist, this {@code exponentialSeparator} has the precedence. * * @serial * @since 1.6 @@ -878,6 +1029,39 @@ */ private Locale locale; + /** + * String representation of per mille sign, which may include + * formatting characters, such as BiDi control characters. + * The first non-format character of this string is the same as + * {@code perMill}. + * + * @serial + * @since 13 + */ + private String perMillText; + + /** + * String representation of percent sign, which may include + * formatting characters, such as BiDi control characters. + * The first non-format character of this string is the same as + * {@code percent}. + * + * @serial + * @since 13 + */ + private String percentText; + + /** + * String representation of minus sign, which may include + * formatting characters, such as BiDi control characters. + * The first non-format character of this string is the same as + * {@code minusSign}. + * + * @serial + * @since 13 + */ + private String minusSignText; + // currency; only the ISO code is serialized. private transient Currency currency; private transient volatile boolean currencyInitialized; @@ -891,23 +1075,28 @@ // monetarySeparator and exponential. // - 2 for version from J2SE 1.4, which includes locale field. // - 3 for version from J2SE 1.6, which includes exponentialSeparator field. - private static final int currentSerialVersion = 3; + // - 4 for version from Java SE 13, which includes perMillText, percentText, + // and minusSignText field. + private static final int currentSerialVersion = 4; /** - * Describes the version of DecimalFormatSymbols present on the stream. + * Describes the version of {@code DecimalFormatSymbols} present on the stream. * Possible values are: *

    - * When streaming out a DecimalFormatSymbols, the most recent format - * (corresponding to the highest allowable serialVersionOnStream) + * When streaming out a {@code DecimalFormatSymbols}, the most recent format + * (corresponding to the highest allowable {@code serialVersionOnStream}) * is always written. * * @serial diff -r 51195881bd3a -r 83deaa8f0c8e test/jdk/java/text/Format/NumberFormat/DFSMinusPerCentMill.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/text/Format/NumberFormat/DFSMinusPerCentMill.java Fri Mar 22 09:31:36 2019 -0700 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8220309 + * @library /java/text/testlib + * @summary Test String representation of MinusSign/Percent/PerMill symbols. + * This test assumes CLDR has numbering systems for "arab" and + * "arabext", and their minus/percent representations include + * BiDi formatting control characters. + * @run testng/othervm DFSMinusPerCentMill + */ + +import java.io.*; +import java.util.*; +import java.text.*; + +import static org.testng.Assert.*; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class DFSMinusPerCentMill { + private enum Type { + NUMBER, PERCENT, CURRENCY, INTEGER, COMPACT, PERMILL + } + + private static final Locale US_ARAB = Locale.forLanguageTag("en-US-u-nu-arab"); + private static final Locale US_ARABEXT = Locale.forLanguageTag("en-US-u-nu-arabext"); + private static final double SRC_NUM = -1234.56; + + @DataProvider + Object[][] formatData() { + return new Object[][] { + // Locale, FormatStyle, expected format, expected single char symbol + {US_ARAB, Type.NUMBER, "\u061c-\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666"}, + {US_ARAB, Type.PERCENT, "\u061c-\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066a\u061c"}, + {US_ARAB, Type.CURRENCY, "\u061c-$\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666"}, + {US_ARAB, Type.INTEGER, "\u061c-\u0661\u066c\u0662\u0663\u0665"}, + {US_ARAB, Type.COMPACT, "\u061c-\u0661K"}, + {US_ARAB, Type.PERMILL, "\u061c-\u0661\u0662\u0663\u0664\u0665\u0666\u0660\u0609"}, + + {US_ARABEXT, Type.NUMBER, "\u200e-\u200e\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"}, + {US_ARABEXT, Type.PERCENT, "\u200e-\u200e\u06f1\u06f2\u06f3\u066c\u06f4\u06f5\u06f6\u066a"}, + {US_ARABEXT, Type.CURRENCY, "\u200e-\u200e$\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"}, + {US_ARABEXT, Type.INTEGER, "\u200e-\u200e\u06f1\u066c\u06f2\u06f3\u06f5"}, + {US_ARABEXT, Type.COMPACT, "\u200e-\u200e\u06f1K"}, + {US_ARABEXT, Type.PERMILL, "\u200e-\u200e\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f0\u0609"}, + }; + } + + @DataProvider + Object[][] charSymbols() { + return new Object[][]{ + // Locale, percent, per mille, minus sign + {US_ARAB, '\u066a', '\u0609', '-'}, + {US_ARABEXT, '\u066a', '\u0609', '-'}, + }; + } + + @Test(dataProvider="formatData") + public void testFormatData(Locale l, Type style, String expected) { + NumberFormat nf = null; + switch (style) { + case NUMBER: + nf = NumberFormat.getNumberInstance(l); + break; + case PERCENT: + nf = NumberFormat.getPercentInstance(l); + break; + case CURRENCY: + nf = NumberFormat.getCurrencyInstance(l); + break; + case INTEGER: + nf = NumberFormat.getIntegerInstance(l); + break; + case COMPACT: + nf = NumberFormat.getCompactNumberInstance(l, NumberFormat.Style.SHORT); + break; + case PERMILL: + nf = new DecimalFormat("#.#\u2030", DecimalFormatSymbols.getInstance(l)); + break; + } + + assertEquals(nf.format(SRC_NUM), expected); + } + + @Test(dataProvider="charSymbols") + public void testCharSymbols(Locale l, char percent, char permill, char minus) { + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l); + assertEquals(dfs.getPercent(), percent); + assertEquals(dfs.getPerMill(), permill); + assertEquals(dfs.getMinusSign(), minus); + } + + @Test + public void testSerialization() throws Exception { + DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + new ObjectOutputStream(bos).writeObject(dfs); + DecimalFormatSymbols dfsSerialized = (DecimalFormatSymbols)new ObjectInputStream( + new ByteArrayInputStream(bos.toByteArray()) + ).readObject(); + + assertEquals(dfs, dfsSerialized); + + // set minus/percent/permille + dfs.setMinusSign('a'); + dfs.setPercent('b'); + dfs.setPerMill('c'); + bos = new ByteArrayOutputStream(); + new ObjectOutputStream(bos).writeObject(dfs); + dfsSerialized = (DecimalFormatSymbols)new ObjectInputStream( + new ByteArrayInputStream(bos.toByteArray()) + ).readObject(); + + assertEquals(dfs, dfsSerialized); + } +}