< 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 ****
/*
! * 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
--- 1,7 ----
/*
! * 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,227 ****
* Sets the character used for zero. Different for Arabic, etc.
*
* @param zeroDigit the character used for zero
*/
public void setZeroDigit(char zeroDigit) {
this.zeroDigit = zeroDigit;
}
/**
! * Gets the character used for thousands 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.
*
* @param groupingSeparator the grouping separator
*/
public void setGroupingSeparator(char groupingSeparator) {
this.groupingSeparator = groupingSeparator;
}
/**
* Gets the character used for decimal sign. Different for French, etc.
--- 200,229 ----
* 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 grouping separator. Different for French, etc.
*
* @return the grouping separator
*/
public char getGroupingSeparator() {
return groupingSeparator;
}
/**
! * 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,245 ****
--- 238,248 ----
* 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,302 ****
* 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) {
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() {
--- 257,272 ----
* 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 character used for percent sign. Different for Arabic, etc.
*
* @return the character used for percent sign
*/
public char getPercent() {
*** 307,355 ****
* Sets the character used for percent sign. Different for Arabic, etc.
*
* @param percent the character used for percent sign
*/
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, '%');
- }
-
- /**
* Gets the character used for a digit in a pattern.
*
* @return the character used for a digit in a pattern
*/
public char getDigit() {
--- 277,292 ----
* 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 character used for a digit in a pattern.
*
* @return the character used for a digit in a pattern
*/
public char getDigit() {
*** 360,369 ****
--- 297,307 ----
* 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,389 ****
--- 318,328 ----
* 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,409 ****
--- 339,349 ----
* 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,429 ****
--- 360,370 ----
* 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,495 ****
* minusSign to the positive format.
*
* @param minusSign the character representing minus sign
*/
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, '-');
- }
-
- /**
* Returns the currency symbol for the currency of these
* DecimalFormatSymbols in their locale.
*
* @return the currency symbol
* @since 1.2
--- 383,398 ----
* 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);
}
/**
* Returns the currency symbol for the currency of these
* DecimalFormatSymbols in their locale.
*
* @return the currency symbol
* @since 1.2
*** 508,517 ****
--- 411,421 ----
* @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,552 ****
--- 447,457 ----
* @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,593 ****
--- 489,499 ----
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,633 ****
* @param sep the monetary decimal separator
* @since 1.2
*/
public void setMonetaryDecimalSeparator(char sep)
{
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
--- 515,528 ----
* @param sep the monetary decimal separator
* @since 1.2
*/
public void setMonetaryDecimalSeparator(char sep)
{
+ hashCode = 0;
monetarySeparator = sep;
}
/**
* 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,655 ****
{
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
--- 533,542 ----
*** 659,671 ****
--- 546,715 ----
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,728 ****
NaN.equals(other.NaN) &&
getCurrencySymbol().equals(other.getCurrencySymbol()) && // possible currency init occurs here
intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
currency == other.currency &&
monetarySeparator == other.monetarySeparator &&
exponentialSeparator.equals(other.exponentialSeparator) &&
locale.equals(other.locale));
}
/**
* Override hashCode.
*/
@Override
public int hashCode() {
! int result = zeroDigit;
! result = result * 37 + groupingSeparator;
! result = result * 37 + decimalSeparator;
! return result;
}
/**
* Initializes the symbols from the FormatData resource bundle.
*/
--- 750,794 ----
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() {
! 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,774 ****
perMillText = numberElements[8];
perMill = findNonFormatChar(perMillText, '\u2030');
infinity = numberElements[9];
NaN = numberElements[10];
// 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
*/
--- 823,841 ----
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];
}
/**
* Obtains non-format single character from String
*/
*** 842,851 ****
--- 909,920 ----
* {@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,893 ****
--- 953,966 ----
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,915 ****
* @see #getZeroDigit
*/
private char zeroDigit;
/**
! * Character used for thousands separator.
*
* @serial
* @see #getGroupingSeparator
*/
private char groupingSeparator;
--- 978,988 ----
* @see #getZeroDigit
*/
private char zeroDigit;
/**
! * Character used for grouping separator.
*
* @serial
* @see #getGroupingSeparator
*/
private char groupingSeparator;
*** 1061,1070 ****
--- 1134,1151 ----
* @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,1087 ****
// 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;
/**
* Describes the version of {@code DecimalFormatSymbols} present on the stream.
* Possible values are:
* <ul>
--- 1158,1169 ----
// 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.
! // - 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,1104 ****
* <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>
* When streaming out a {@code DecimalFormatSymbols}, the most recent format
* (corresponding to the highest allowable {@code serialVersionOnStream})
* is always written.
*
* @serial
--- 1176,1188 ----
* <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.
! * <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 >