--- old/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java 2020-01-02 21:47:25.000000000 -0800 +++ new/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java 2020-01-02 21:47:25.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -72,7 +72,9 @@ "NumberElements/exponential", "NumberElements/permille", "NumberElements/infinity", - "NumberElements/nan" + "NumberElements/nan", + "NumberElements/currencyDecimal", + "NumberElements/currencyGroup", }; private final static String[] TIME_PATTERN_KEYS = { @@ -810,7 +812,10 @@ assert keys == NUMBER_ELEMENT_KEYS; if (key.endsWith("/pattern")) { numArray[idx] = "#"; - } else { + } else if (!key.endsWith("currencyDecimal") && + !key.endsWith("currencyGroup")) { + // throw error unless it is for "currencyDecimal/Group", + // which may be missing. throw new InternalError("NumberElements: null for " + key + ", id: " + id); } --- old/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java 2020-01-02 21:47:27.000000000 -0800 +++ new/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java 2020-01-02 21:47:27.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -644,19 +644,13 @@ } break; case "decimal": - // for FormatData - // copy string for later assembly into NumberElements - if (currentContainer.getqName().equals("symbols")) { - pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/decimal"); - } else { - pushIgnoredContainer(qName); - } - break; case "group": + case "currencyDecimal": + case "currencyGroup": // for FormatData // copy string for later assembly into NumberElements if (currentContainer.getqName().equals("symbols")) { - pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/group"); + pushStringEntry(qName, attributes, currentNumberingSystem + "NumberElements/" + qName); } else { pushIgnoredContainer(qName); } --- old/src/java.base/share/classes/java/text/DecimalFormat.java 2020-01-02 21:47:29.000000000 -0800 +++ new/src/java.base/share/classes/java/text/DecimalFormat.java 2020-01-02 21:47:29.000000000 -0800 @@ -1,5 +1,5 @@ /* - * 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 @@ -136,14 +136,14 @@ * 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 + * grouping 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 {@code DecimalFormat.parse()} to be able * to distinguish positive from negative values. (If they are identical, then * {@code DecimalFormat} will behave as if no negative subpattern was - * specified.) Another example is that the decimal separator and thousands + * specified.) Another example is that the decimal separator and grouping * separator should be distinct characters, or parsing will be impossible. * *

The grouping separator is commonly used for thousands, but in some @@ -203,7 +203,7 @@ * {@code ,} * Number * Yes - * Grouping separator + * Grouping separator or monetary grouping separator * * {@code E} * Number @@ -231,8 +231,8 @@ * No * Currency sign, replaced by currency symbol. If * doubled, replaced by international currency symbol. - * If present in a pattern, the monetary decimal separator - * is used instead of the decimal separator. + * If present in a pattern, the monetary decimal/grouping separators + * are used instead of the decimal/grouping separators. * * {@code '} * Prefix or suffix @@ -1103,7 +1103,9 @@ // Sets up the locale specific constants used when formatting. // '0' is our default representation of zero. fastPathData.zeroDelta = symbols.getZeroDigit() - '0'; - fastPathData.groupingChar = symbols.getGroupingSeparator(); + fastPathData.groupingChar = isCurrencyFormat ? + symbols.getMonetaryGroupingSeparator() : + symbols.getGroupingSeparator(); // Sets up fractional constants related to currency/decimal pattern. fastPathData.fractionalMaxIntBound = (isCurrencyFormat) @@ -1774,7 +1776,9 @@ int maxIntDigits, int minIntDigits, int maxFraDigits, int minFraDigits) { - char grouping = symbols.getGroupingSeparator(); + char grouping = isCurrencyFormat ? + symbols.getMonetaryGroupingSeparator() : + symbols.getGroupingSeparator(); char zero = symbols.getZeroDigit(); int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero @@ -2393,7 +2397,9 @@ char decimal = isCurrencyFormat ? symbols.getMonetaryDecimalSeparator() : symbols.getDecimalSeparator(); - char grouping = symbols.getGroupingSeparator(); + char grouping = isCurrencyFormat ? + symbols.getMonetaryGroupingSeparator() : + symbols.getGroupingSeparator(); String exponentString = symbols.getExponentSeparator(); boolean sawDecimal = false; boolean sawExponent = false; @@ -4061,7 +4067,7 @@ /** * True if this object represents a currency format. This determines - * whether the monetary decimal separator is used instead of the normal one. + * whether the monetary decimal/grouping separators are used instead of the normal ones. */ private transient boolean isCurrencyFormat = false; @@ -4346,8 +4352,8 @@ * The CURRENCY_SIGN is the standard Unicode symbol for currency. It * is used in patterns and substituted with either the currency symbol, * or if it is doubled, with the international currency symbol. If the - * CURRENCY_SIGN is seen in a pattern, then the decimal separator is - * replaced with the monetary decimal separator. + * CURRENCY_SIGN is seen in a pattern, then the decimal/grouping separators + * are replaced with the monetary decimal/grouping separators. * * The CURRENCY_SIGN is not localized. */ --- old/src/java.base/share/classes/java/text/DecimalFormatSymbols.java 2020-01-02 21:47:31.000000000 -0800 +++ new/src/java.base/share/classes/java/text/DecimalFormatSymbols.java 2020-01-02 21:47:30.000000000 -0800 @@ -1,5 +1,5 @@ /* - * 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 @@ -202,11 +202,12 @@ * @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 */ @@ -215,11 +216,12 @@ } /** - * 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; } @@ -238,6 +240,7 @@ * @param decimalSeparator the character used for decimal sign */ public void setDecimalSeparator(char decimalSeparator) { + hashCode = 0; this.decimalSeparator = decimalSeparator; } @@ -256,45 +259,12 @@ * @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 @@ -309,45 +279,12 @@ * @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 @@ -362,6 +299,7 @@ * @param digit the character used for a digit in a pattern */ public void setDigit(char digit) { + hashCode = 0; this.digit = digit; } @@ -382,6 +320,7 @@ * @param patternSeparator the pattern separator */ public void setPatternSeparator(char patternSeparator) { + hashCode = 0; this.patternSeparator = patternSeparator; } @@ -402,6 +341,7 @@ * @param infinity the string representing infinity */ public void setInfinity(String infinity) { + hashCode = 0; this.infinity = infinity; } @@ -422,6 +362,7 @@ * @param NaN the string representing "not a number" */ public void setNaN(String NaN) { + hashCode = 0; this.NaN = NaN; } @@ -444,50 +385,12 @@ * @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. * @@ -510,6 +413,7 @@ public void setCurrencySymbol(String currency) { initializeCurrency(locale); + hashCode = 0; currencySymbol = currency; } @@ -545,6 +449,7 @@ public void setInternationalCurrencySymbol(String currencyCode) { initializeCurrency(locale); + hashCode = 0; intlCurrencySymbol = currencyCode; currency = null; if (currencyCode != null) { @@ -586,6 +491,7 @@ throw new NullPointerException(); } initializeCurrency(locale); + hashCode = 0; this.currency = currency; intlCurrencySymbol = currency.getCurrencyCode(); currencySymbol = currency.getSymbol(locale); @@ -611,21 +517,10 @@ */ 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. @@ -640,14 +535,6 @@ } /** - * 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. * @@ -661,9 +548,166 @@ 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 @@ -692,35 +736,57 @@ if (getClass() != obj.getClass()) return false; DecimalFormatSymbols other = (DecimalFormatSymbols) obj; return (zeroDigit == other.zeroDigit && - 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) && - 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)); + 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) && + 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; } /** @@ -759,14 +825,15 @@ 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; } /** @@ -844,6 +911,8 @@ * {@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. @@ -886,6 +955,10 @@ "per mille, and/or minus sign disagree."); } } + if (serialVersionOnStream < 5) { + // didn't have monetaryGroupingSeparator. Create one using groupingSeparator + monetaryGroupingSeparator = groupingSeparator; + } serialVersionOnStream = currentSerialVersion; @@ -907,7 +980,7 @@ private char zeroDigit; /** - * Character used for thousands separator. + * Character used for grouping separator. * * @serial * @see #getGroupingSeparator @@ -1063,6 +1136,14 @@ */ 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; @@ -1079,7 +1160,8 @@ // - 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. @@ -1096,7 +1178,9 @@ *

  • 4: Versions written by Java SE 13 or later, which include * new {@code perMillText}, {@code percentText}, and * {@code minusSignText} field. - * + *
  • 5: Versions written by Java SE 15 or later, which include + * new {@code monetaryGroupingSeparator} field. + * * * When streaming out a {@code DecimalFormatSymbols}, the most recent format * (corresponding to the highest allowable {@code serialVersionOnStream}) * is always written. --- old/test/jdk/java/text/Format/NumberFormat/CurrencyFormat.java 2020-01-02 21:47:33.000000000 -0800 +++ new/test/jdk/java/text/Format/NumberFormat/CurrencyFormat.java 2020-01-02 21:47:33.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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 @@ -23,10 +23,11 @@ /* * @test - * @bug 4290801 4942982 5102005 8008577 8021121 8210153 + * @bug 4290801 4942982 5102005 8008577 8021121 8210153 8227313 * @summary Basic tests for currency formatting. * @modules jdk.localedata - * @run main/othervm -Djava.locale.providers=JRE,SPI CurrencyFormat + * @run main/othervm -Djava.locale.providers=COMPAT CurrencyFormat COMPAT + * @run main/othervm -Djava.locale.providers=CLDR CurrencyFormat CLDR */ import java.io.File; @@ -42,7 +43,10 @@ public class CurrencyFormat { + private static boolean isCompat; + public static void main(String[] args) throws Exception { + isCompat = "COMPAT".equals(args[0]); testFormatting(); testSymbols(); } @@ -54,7 +58,10 @@ Locale.JAPAN, Locale.GERMANY, Locale.ITALY, - new Locale("it", "IT", "EURO") }; + new Locale("it", "IT", "EURO"), + Locale.forLanguageTag("de-AT"), + Locale.forLanguageTag("fr-CH"), + }; Currency[] currencies = { null, Currency.getInstance("USD"), @@ -68,6 +75,17 @@ {"1.234,56 \u20AC", "1.234,56 USD", "1.235 JPY", "1.234,56 DM", "1.234,56 \u20AC"}, {"\u20AC 1.234,56", "USD 1.234,56", "JPY 1.235", "DEM 1.234,56", "\u20AC 1.234,56"}, {"\u20AC 1.234,56", "USD 1.234,56", "JPY 1.235", "DEM 1.234,56", "\u20AC 1.234,56"}, + {"\u20AC 1.234,56", "USD 1.234,56", "JPY 1.235", "DEM 1.234,56", "\u20AC 1.234,56"}, + {"SFr. 1'234.56", "USD 1'234.56", "JPY 1'235", "DEM 1'234.56", "EUR 1'234.56"}, + }; + String[][] expecteds_cldr = { + {"$1,234.56", "$1,234.56", "\u00a51,235", "DEM1,234.56", "\u20ac1,234.56"}, + {"\uFFE51,235", "$1,234.56", "\uFFE51,235", "DEM1,234.56", "\u20ac1,234.56"}, + {"1.234,56\u00a0\u20ac", "1.234,56\u00a0$", "1.235\u00a0\u00a5", "1.234,56\u00a0DM", "1.234,56\u00a0\u20ac"}, + {"1.234,56\u00a0\u20ac", "1.234,56\u00a0USD", "1.235\u00a0JPY", "1.234,56\u00a0DEM", "1.234,56\u00a0\u20ac"}, + {"1.234,56\u00a0\u20ac", "1.234,56\u00a0USD", "1.235\u00a0JPY", "1.234,56\u00a0DEM", "1.234,56\u00a0\u20ac"}, + {"\u20ac\u00a01.234,56", "$\u00a01.234,56", "\u00a5\u00a01.235", "DM\u00a01.234,56", "\u20ac\u00a01.234,56"}, + {"1\u202f234.56\u00a0CHF", "1\u202f234.56\u00a0$US", "1\u202f235\u00a0JPY", "1\u202f234.56\u00a0DEM", "1\u202f234.56\u00a0\u20ac"}, }; for (int i = 0; i < locales.length; i++) { @@ -75,7 +93,7 @@ NumberFormat format = NumberFormat.getCurrencyInstance(locale); for (int j = 0; j < currencies.length; j++) { Currency currency = currencies[j]; - String expected = expecteds[i][j]; + String expected = isCompat ? expecteds[i][j] : expecteds_cldr[i][j]; if (currency != null) { format.setCurrency(currency); int digits = currency.getDefaultFractionDigits(); @@ -99,6 +117,11 @@ } static void testSymbols() throws Exception { + if (!isCompat) { + // For COMPAT only. + return; + } + FileInputStream stream = new FileInputStream(new File(System.getProperty("test.src", "."), "CurrencySymbols.properties")); Properties props = new Properties(); props.load(stream); --- old/test/jdk/java/text/Format/NumberFormat/NumberRegression.java 2020-01-02 21:47:36.000000000 -0800 +++ new/test/jdk/java/text/Format/NumberFormat/NumberRegression.java 2020-01-02 21:47:35.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -29,7 +29,7 @@ * 4098741 4099404 4101481 4106658 4106662 4106664 4108738 4110936 4122840 * 4125885 4134034 4134300 4140009 4141750 4145457 4147295 4147706 4162198 * 4162852 4167494 4170798 4176114 4179818 4185761 4212072 4212073 4216742 - * 4217661 4243011 4243108 4330377 4233840 4241880 4833877 8008577 + * 4217661 4243011 4243108 4330377 4233840 4241880 4833877 8008577 8227313 * @summary Regression tests for NumberFormat and associated classes * @library /java/text/testlib * @build IntlTest HexDumpReader TestUtils @@ -1802,6 +1802,66 @@ } Locale.setDefault(savedLocale); } + + /** + * Test for get/setMonetaryGroupingSeparator() methods. + * @since 15 + */ + public void test8227313() throws ParseException { + var nrmSep = 'n'; + var monSep = 'm'; + var curSym = "Cur"; + var inputNum = 10; + var nrmPattern = ",#"; + var monPattern = "\u00a4 ,#"; + var expectedNrmFmt = "1n0"; + var expectedMonFmt = "Cur 1m0"; + + var ndfs = DecimalFormatSymbols.getInstance(); + ndfs.setGroupingSeparator(nrmSep); + var nf = new DecimalFormat(nrmPattern, ndfs); + var mdfs = DecimalFormatSymbols.getInstance(); + mdfs.setMonetaryGroupingSeparator(monSep); + mdfs.setCurrencySymbol(curSym); + var mf = new DecimalFormat(monPattern, mdfs); + + // get test + char gotNrmSep = mdfs.getGroupingSeparator(); + char gotMonSep = mdfs.getMonetaryGroupingSeparator(); + if (gotMonSep != monSep) { + errln("FAIL: getMonetaryGroupingSeparator() returned incorrect value. expected: " + + monSep + ", got: " + gotMonSep); + } + if (gotMonSep == gotNrmSep) { + errln("FAIL: getMonetaryGroupingSeparator() returned the same value with " + + "getGroupingSeparator(): monetary sep: " + gotMonSep + + ", normal sep: " + gotNrmSep); + } + + // format test + var formatted = mf.format(inputNum); + if (!formatted.equals(expectedMonFmt)) { + errln("FAIL: format failed. expected: " + expectedMonFmt + + ", got: " + formatted); + } + formatted = nf.format(inputNum); + if (!formatted.equals(expectedNrmFmt)) { + errln("FAIL: normal format failed. expected: " + expectedNrmFmt + + ", got: " + formatted); + } + + // parse test + Number parsed = mf.parse(expectedMonFmt); + if (parsed.intValue() != inputNum) { + errln("FAIL: parse failed. expected: " + inputNum + + ", got: " + parsed); + } + parsed = nf.parse(expectedNrmFmt); + if (parsed.intValue() != inputNum) { + errln("FAIL: normal parse failed. expected: " + inputNum + + ", got: " + parsed); + } + } } @SuppressWarnings("serial") --- old/test/jdk/sun/text/resources/LocaleData.cldr 2020-01-02 21:47:37.000000000 -0800 +++ new/test/jdk/sun/text/resources/LocaleData.cldr 2020-01-02 21:47:37.000000000 -0800 @@ -8388,3 +8388,9 @@ FormatData/es_CL/standalone.MonthAbbreviations/8=sept. FormatData/es_CO/MonthAbbreviations/8=sep. FormatData/es_CO/standalone.MonthAbbreviations/8=sept. + +# bug # 8227313 +FormatData/de/latn.NumberElements/12= +FormatData/de_AT/latn.NumberElements/12=. +FormatData/fr/latn.NumberElements/11= +FormatData/fr_CH/latn.NumberElements/11=.