8282625: Formatter caches Locale/DecimalFormatSymbols poorly

Reviewed-by: naoto, rriggs, jpai
This commit is contained in:
Jim Laskey 2022-03-22 15:32:46 +00:00
parent fabde3b7b8
commit 557ff4b355
3 changed files with 58 additions and 42 deletions
src/java.base/share/classes/java
test/jdk/java/text/Format/NumberFormat

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2022, 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
@ -188,6 +188,15 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
return dfsyms;
}
/**
* {@return locale used to create this instance}
*
* @since 19
*/
public Locale getLocale() {
return locale;
}
/**
* Gets the character used for zero. Different for Arabic, etc.
*

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2022, 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
@ -2008,15 +2008,41 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter;
* @since 1.5
*/
public final class Formatter implements Closeable, Flushable {
// Caching DecimalFormatSymbols. Non-volatile to avoid thread slamming.
private static DecimalFormatSymbols DFS = null;
private static DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
// Capture local copy to avoid thread race.
DecimalFormatSymbols dfs = DFS;
if (dfs != null && dfs.getLocale().equals(locale)) {
return dfs;
}
// Fetch a new local instance of DecimalFormatSymbols. Note that DFS are mutable
// and this instance is reserved for Formatter.
dfs = DecimalFormatSymbols.getInstance(locale);
// Non-volatile here is acceptable heuristic.
DFS = dfs;
return dfs;
}
// Use zero from cached DecimalFormatSymbols.
private static char getZero(Locale locale) {
return locale == null ? '0' : getDecimalFormatSymbols(locale).getZeroDigit();
}
// Use decimal separator from cached DecimalFormatSymbols.
private static char getDecimalSeparator(Locale locale) {
return locale == null ? '.' : getDecimalFormatSymbols(locale).getDecimalSeparator();
}
// Use grouping separator from cached DecimalFormatSymbols.
private static char getGroupingSeparator(Locale locale) {
return locale == null ? ',' : getDecimalFormatSymbols(locale).getGroupingSeparator();
}
private Appendable a;
private final Locale l;
private IOException lastException;
// Non-character value used to mark zero as uninitialized
private static final char ZERO_SENTINEL = '\uFFFE';
private char zero = ZERO_SENTINEL;
/**
* Returns a charset object for the given charset name.
* @throws NullPointerException is csn is null
@ -2523,20 +2549,6 @@ public final class Formatter implements Closeable, Flushable {
this(l, new BufferedWriter(new OutputStreamWriter(os, charset)));
}
private char zero() {
char zero = this.zero;
if (zero == ZERO_SENTINEL) {
if ((l != null) && !l.equals(Locale.US)) {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
zero = dfs.getZeroDigit();
} else {
zero = '0';
}
this.zero = zero;
}
return zero;
}
/**
* Returns the locale set by the construction of this formatter.
*
@ -4498,14 +4510,6 @@ public final class Formatter implements Closeable, Flushable {
throw new IllegalFormatConversionException(c, arg.getClass());
}
private char getZero(Formatter fmt, Locale l) {
if ((l != null) && !l.equals(fmt.locale())) {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
return dfs.getZeroDigit();
}
return fmt.zero();
}
private StringBuilder localizedMagnitude(Formatter fmt, StringBuilder sb,
long value, int flags, int width, Locale l) {
return localizedMagnitude(fmt, sb, Long.toString(value, 10), 0, flags, width, l);
@ -4519,7 +4523,7 @@ public final class Formatter implements Closeable, Flushable {
}
int begin = sb.length();
char zero = getZero(fmt, l);
char zero = getZero(l);
// determine localized grouping separator and size
char grpSep = '\0';
@ -4536,21 +4540,15 @@ public final class Formatter implements Closeable, Flushable {
}
if (dot < len) {
if (l == null || l.equals(Locale.US)) {
decSep = '.';
} else {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
decSep = dfs.getDecimalSeparator();
}
decSep = getDecimalSeparator(l);
}
if (Flags.contains(f, Flags.GROUP)) {
grpSep = getGroupingSeparator(l);
if (l == null || l.equals(Locale.US)) {
grpSep = ',';
grpSize = 3;
} else {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
grpSep = dfs.getGroupingSeparator();
DecimalFormat df = null;
NumberFormat nf = NumberFormat.getNumberInstance(l);
if (nf instanceof DecimalFormat) {
@ -4567,7 +4565,7 @@ public final class Formatter implements Closeable, Flushable {
}
String[] all = adapter.getLocaleResources(l)
.getNumberPatterns();
df = new DecimalFormat(all[0], dfs);
df = new DecimalFormat(all[0], getDecimalFormatSymbols(l));
}
grpSize = df.getGroupingSize();
// Some locales do not use grouping (the number
@ -4612,7 +4610,7 @@ public final class Formatter implements Closeable, Flushable {
// group separators is added for any locale.
private void localizedMagnitudeExp(Formatter fmt, StringBuilder sb, char[] value,
final int offset, Locale l) {
char zero = getZero(fmt, l);
char zero = getZero(l);
int len = value.length;
for (int j = offset; j < len; j++) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022, 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,6 +23,7 @@
/*
* @test
* @bug 8282625
* @library /java/text/testlib
* @summary test International Decimal Format Symbols
*/
@ -60,6 +61,14 @@ public class IntlTestDecimalFormatSymbols extends IntlTest
// just do some VERY basic tests to make sure that get/set work
if (!fr.getLocale().equals(Locale.FRENCH)) {
errln("ERROR: French DecimalFormatSymbols not Locale.FRENCH");
}
if (!en.getLocale().equals(Locale.ENGLISH)) {
errln("ERROR: English DecimalFormatSymbols not Locale.ENGLISH");
}
char zero = en.getZeroDigit();
fr.setZeroDigit(zero);
if(fr.getZeroDigit() != en.getZeroDigit()) {