--- old/src/java.base/share/classes/java/text/DecimalFormatSymbols.java 2019-03-19 14:44:56.794370913 -0700 +++ new/src/java.base/share/classes/java/text/DecimalFormatSymbols.java 2019-03-19 14:44:55.987353302 -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; @@ -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, '-'); } /** @@ -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) { @@ -712,10 +840,16 @@ * initializes localeto the root locale, and initializes * If serialVersionOnStream is less than 3, it initializes * exponentialSeparator using exponential. + * If serialVersionOnStream is less than 4, it initializes + * perMillText, percentText, and + * minusSignText using perMill, percent, and + * minusSign respectively. * Sets 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 char and 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) { @@ -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 + * 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 + * 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 + * minusSign. + * + * @serial + * @since 13 + */ + private String minusSignText; + // currency; only the ISO code is serialized. private transient Currency currency; private transient volatile boolean currencyInitialized; @@ -891,7 +1075,9 @@ // 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. @@ -905,6 +1091,9 @@ * new locale field. *
  • 3: Versions written by J2SE 1.6 or later, which include a * new exponentialSeparator field. + *
  • 4: Versions written by Java SE 13 or later, which include + * new perMillText, percentText, and + * minusSignText field. * * When streaming out a DecimalFormatSymbols, the most recent format * (corresponding to the highest allowable serialVersionOnStream)