< prev index next >

src/java.base/share/classes/java/text/DecimalFormatSymbols.java

Print this page
rev 57525 : 8227313: Support monetary grouping separator in DecimalFormat/DecimalFormatSymbols
Reviewed-by: joehw

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2020, 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.  Oracle designates this

@@ -200,28 +200,30 @@
      * Sets the character used for zero. Different for Arabic, etc.
      *
      * @param zeroDigit the character used for zero
      */
     public void setZeroDigit(char zeroDigit) {
+        hashCode = 0;
         this.zeroDigit = zeroDigit;
     }
 
     /**
-     * Gets the character used for thousands separator. Different for French, etc.
+     * Gets the character used for grouping separator. Different for French, etc.
      *
      * @return the grouping separator
      */
     public char getGroupingSeparator() {
         return groupingSeparator;
     }
 
     /**
-     * Sets the character used for thousands separator. Different for French, etc.
+     * Sets the character used for grouping separator. Different for French, etc.
      *
      * @param groupingSeparator the grouping separator
      */
     public void setGroupingSeparator(char groupingSeparator) {
+        hashCode = 0;
         this.groupingSeparator = groupingSeparator;
     }
 
     /**
      * Gets the character used for decimal sign. Different for French, etc.

@@ -236,10 +238,11 @@
      * Sets the character used for decimal sign. Different for French, etc.
      *
      * @param decimalSeparator the character used for decimal sign
      */
     public void setDecimalSeparator(char decimalSeparator) {
+        hashCode = 0;
         this.decimalSeparator = decimalSeparator;
     }
 
     /**
      * Gets the character used for per mille sign. Different for Arabic, etc.

@@ -254,49 +257,16 @@
      * Sets the character used for per mille sign. Different for Arabic, etc.
      *
      * @param perMill the character used for per mille sign
      */
     public void setPerMill(char perMill) {
+        hashCode = 0;
         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');
-    }
-
-    /**
      * Gets the character used for percent sign. Different for Arabic, etc.
      *
      * @return the character used for percent sign
      */
     public char getPercent() {

@@ -307,49 +277,16 @@
      * Sets the character used for percent sign. Different for Arabic, etc.
      *
      * @param percent the character used for percent sign
      */
     public void setPercent(char percent) {
+        hashCode = 0;
         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, '%');
-    }
-
-    /**
      * Gets the character used for a digit in a pattern.
      *
      * @return the character used for a digit in a pattern
      */
     public char getDigit() {

@@ -360,10 +297,11 @@
      * Sets the character used for a digit in a pattern.
      *
      * @param digit the character used for a digit in a pattern
      */
     public void setDigit(char digit) {
+        hashCode = 0;
         this.digit = digit;
     }
 
     /**
      * Gets the character used to separate positive and negative subpatterns

@@ -380,10 +318,11 @@
      * in a pattern.
      *
      * @param patternSeparator the pattern separator
      */
     public void setPatternSeparator(char patternSeparator) {
+        hashCode = 0;
         this.patternSeparator = patternSeparator;
     }
 
     /**
      * Gets the string used to represent infinity. Almost always left

@@ -400,10 +339,11 @@
      * unchanged.
      *
      * @param infinity the string representing infinity
      */
     public void setInfinity(String infinity) {
+        hashCode = 0;
         this.infinity = infinity;
     }
 
     /**
      * Gets the string used to represent "not a number". Almost always left

@@ -420,10 +360,11 @@
      * unchanged.
      *
      * @param NaN the string representing "not a number"
      */
     public void setNaN(String NaN) {
+        hashCode = 0;
         this.NaN = NaN;
     }
 
     /**
      * Gets the character used to represent minus sign. If no explicit

@@ -442,54 +383,16 @@
      * minusSign to the positive format.
      *
      * @param minusSign the character representing minus sign
      */
     public void setMinusSign(char minusSign) {
+        hashCode = 0;
         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, '-');
-    }
-
-    /**
      * Returns the currency symbol for the currency of these
      * DecimalFormatSymbols in their locale.
      *
      * @return the currency symbol
      * @since 1.2

@@ -508,10 +411,11 @@
      * @since 1.2
      */
     public void setCurrencySymbol(String currency)
     {
         initializeCurrency(locale);
+        hashCode = 0;
         currencySymbol = currency;
     }
 
     /**
      * Returns the ISO 4217 currency code of the currency of these

@@ -543,10 +447,11 @@
      * @since 1.2
      */
     public void setInternationalCurrencySymbol(String currencyCode)
     {
         initializeCurrency(locale);
+        hashCode = 0;
         intlCurrencySymbol = currencyCode;
         currency = null;
         if (currencyCode != null) {
             try {
                 currency = Currency.getInstance(currencyCode);

@@ -584,10 +489,11 @@
     public void setCurrency(Currency currency) {
         if (currency == null) {
             throw new NullPointerException();
         }
         initializeCurrency(locale);
+        hashCode = 0;
         this.currency = currency;
         intlCurrencySymbol = currency.getCurrencyCode();
         currencySymbol = currency.getSymbol(locale);
     }
 

@@ -609,25 +515,14 @@
      * @param sep the monetary decimal separator
      * @since 1.2
      */
     public void setMonetaryDecimalSeparator(char sep)
     {
+        hashCode = 0;
         monetarySeparator = sep;
     }
 
-    //------------------------------------------------------------
-    // BEGIN   Package Private methods ... to be made public later
-    //------------------------------------------------------------
-
-    /**
-     * Returns the character used to separate the mantissa from the exponent.
-     */
-    char getExponentialSymbol()
-    {
-        return exponential;
-    }
-
     /**
      * Returns the string used to separate the mantissa from the exponent.
      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
      *
      * @return the exponent separator string

@@ -638,18 +533,10 @@
     {
         return exponentialSeparator;
     }
 
     /**
-     * Sets the character used to separate the mantissa from the exponent.
-     */
-    void setExponentialSymbol(char exp)
-    {
-        exponential = exp;
-    }
-
-    /**
      * Sets the string used to separate the mantissa from the exponent.
      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
      *
      * @param exp the exponent separator string
      * @throws    NullPointerException if {@code exp} is null

@@ -659,13 +546,170 @@
     public void setExponentSeparator(String exp)
     {
         if (exp == null) {
             throw new NullPointerException();
         }
+        hashCode = 0;
         exponentialSeparator = exp;
     }
 
+    /**
+     * Gets the character used for grouping separator for currencies.
+     * May be different from {@code grouping separator} in some locales,
+     * e.g, German in Austria.
+     *
+     * @return the monetary grouping separator
+     * @since 15
+     */
+    public char getMonetaryGroupingSeparator() {
+        return monetaryGroupingSeparator;
+    }
+
+    /**
+     * Sets the character used for grouping separator for currencies.
+     * Invocation of this method will not affect the normal
+     * {@code grouping separator}.
+     *
+     * @param monetaryGroupingSeparator the monetary grouping separator
+     * @see #setGroupingSeparator(char)
+     * @since 15
+     */
+    public void setMonetaryGroupingSeparator(char monetaryGroupingSeparator)
+    {
+        hashCode = 0;
+        this.monetaryGroupingSeparator = monetaryGroupingSeparator;
+    }
+
+    //------------------------------------------------------------
+    // BEGIN   Package Private methods ... to be made public later
+    //------------------------------------------------------------
+
+    /**
+     * Returns the character used to separate the mantissa from the exponent.
+     */
+    char getExponentialSymbol()
+    {
+        return exponential;
+    }
+
+    /**
+     * Sets the character used to separate the mantissa from the exponent.
+     */
+    void setExponentialSymbol(char exp)
+    {
+        exponential = exp;
+    }
+
+    /**
+     * 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");
+        }
+
+        hashCode = 0;
+        this.perMillText = perMillText;
+        this.perMill = findNonFormatChar(perMillText, '\u2030');
+    }
+
+    /**
+     * 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");
+        }
+
+        hashCode = 0;
+        this.percentText = percentText;
+        this.percent = findNonFormatChar(percentText, '%');
+    }
+
+    /**
+     * 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");
+        }
+
+        hashCode = 0;
+        this.minusSignText = minusSignText;
+        this.minusSign = findNonFormatChar(minusSignText, '-');
+    }
 
     //------------------------------------------------------------
     // END     Package Private methods ... to be made public later
     //------------------------------------------------------------
 

@@ -706,23 +750,45 @@
         NaN.equals(other.NaN) &&
         getCurrencySymbol().equals(other.getCurrencySymbol()) && // possible currency init occurs here
         intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
         currency == other.currency &&
         monetarySeparator == other.monetarySeparator &&
+            monetaryGroupingSeparator == other.monetaryGroupingSeparator &&
         exponentialSeparator.equals(other.exponentialSeparator) &&
         locale.equals(other.locale));
     }
 
     /**
      * Override hashCode.
      */
+    private volatile int hashCode;
     @Override
     public int hashCode() {
-            int result = zeroDigit;
-            result = result * 37 + groupingSeparator;
-            result = result * 37 + decimalSeparator;
-            return result;
+        if (hashCode == 0) {
+            hashCode = Objects.hash(
+                zeroDigit,
+                groupingSeparator,
+                decimalSeparator,
+                percent,
+                percentText,
+                perMill,
+                perMillText,
+                digit,
+                minusSign,
+                minusSignText,
+                patternSeparator,
+                infinity,
+                NaN,
+                getCurrencySymbol(), // possible currency init occurs here
+                intlCurrencySymbol,
+                currency,
+                monetarySeparator,
+                monetaryGroupingSeparator,
+                exponentialSeparator,
+                locale);
+        }
+        return hashCode;
     }
 
     /**
      * Initializes the symbols from the FormatData resource bundle.
      */

@@ -757,18 +823,19 @@
         perMillText = numberElements[8];
         perMill = findNonFormatChar(perMillText, '\u2030');
         infinity  = numberElements[9];
         NaN = numberElements[10];
 
+        // monetary decimal/grouping separators may be missing in resource bundles
+        monetarySeparator = numberElements.length < 12 || numberElements[11].isEmpty() ?
+            decimalSeparator : numberElements[11].charAt(0);
+        monetaryGroupingSeparator = numberElements.length < 13 || numberElements[12].isEmpty() ?
+            groupingSeparator : numberElements[12].charAt(0);
+
         // maybe filled with previously cached values, or null.
         intlCurrencySymbol = (String) data[1];
         currencySymbol = (String) data[2];
-
-        // Currently the monetary decimal separator is the same as the
-        // standard decimal separator for all locales that we support.
-        // If that changes, add a new entry to NumberElements.
-        monetarySeparator = decimalSeparator;
     }
 
     /**
      * Obtains non-format single character from String
      */

@@ -842,10 +909,12 @@
      * {@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.
+     * If {@code serialVersionOnStream} is less than 5, it initializes
+     * {@code monetaryGroupingSeparator} using {@code groupingSeparator}.
      * 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}

@@ -884,10 +953,14 @@
                 throw new InvalidObjectException(
                     "'char' and 'String' representations of either percent, " +
                     "per mille, and/or minus sign disagree.");
             }
         }
+        if (serialVersionOnStream < 5) {
+            // didn't have monetaryGroupingSeparator. Create one using groupingSeparator
+            monetaryGroupingSeparator = groupingSeparator;
+        }
 
         serialVersionOnStream = currentSerialVersion;
 
         if (intlCurrencySymbol != null) {
             try {

@@ -905,11 +978,11 @@
      * @see #getZeroDigit
      */
     private  char    zeroDigit;
 
     /**
-     * Character used for thousands separator.
+     * Character used for grouping separator.
      *
      * @serial
      * @see #getGroupingSeparator
      */
     private  char    groupingSeparator;

@@ -1061,10 +1134,18 @@
      * @serial
      * @since 13
      */
     private  String minusSignText;
 
+    /**
+     * The grouping separator used when formatting currency values.
+     *
+     * @serial
+     * @since 15
+     */
+    private  char    monetaryGroupingSeparator;
+
     // currency; only the ISO code is serialized.
     private transient Currency currency;
     private transient volatile boolean currencyInitialized;
 
     // Proclaim JDK 1.1 FCS compatibility

@@ -1077,11 +1158,12 @@
     //     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.
     // - 4 for version from Java SE 13, which includes perMillText, percentText,
     //      and minusSignText field.
-    private static final int currentSerialVersion = 4;
+    // - 5 for version from Java SE 15, which includes monetaryGroupingSeparator.
+    private static final int currentSerialVersion = 5;
 
     /**
      * Describes the version of {@code DecimalFormatSymbols} present on the stream.
      * Possible values are:
      * <ul>

@@ -1094,11 +1176,13 @@
      * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
      *      new {@code exponentialSeparator} field.
      * <li><b>4</b>: Versions written by Java SE 13 or later, which include
      *      new {@code perMillText}, {@code percentText}, and
      *      {@code minusSignText} field.
-     * </ul>
+     * <li><b>5</b>: Versions written by Java SE 15 or later, which include
+     *      new {@code monetaryGroupingSeparator} field.
+     * * </ul>
      * When streaming out a {@code DecimalFormatSymbols}, the most recent format
      * (corresponding to the highest allowable {@code serialVersionOnStream})
      * is always written.
      *
      * @serial
< prev index next >