--- old/src/java.base/share/classes/java/text/DecimalFormatSymbols.java 2019-03-21 08:43:00.698098196 -0700 +++ new/src/java.base/share/classes/java/text/DecimalFormatSymbols.java 2019-03-21 08:42:59.845078506 -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