Skip to content

Commit 557ff4b

Browse files
author
Jim Laskey
committedMar 22, 2022
8282625: Formatter caches Locale/DecimalFormatSymbols poorly
Reviewed-by: naoto, rriggs, jpai
1 parent fabde3b commit 557ff4b

File tree

3 files changed

+58
-42
lines changed

3 files changed

+58
-42
lines changed
 

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -188,6 +188,15 @@ public static final DecimalFormatSymbols getInstance(Locale locale) {
188188
return dfsyms;
189189
}
190190

191+
/**
192+
* {@return locale used to create this instance}
193+
*
194+
* @since 19
195+
*/
196+
public Locale getLocale() {
197+
return locale;
198+
}
199+
191200
/**
192201
* Gets the character used for zero. Different for Arabic, etc.
193202
*

‎src/java.base/share/classes/java/util/Formatter.java

+38-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -2008,15 +2008,41 @@
20082008
* @since 1.5
20092009
*/
20102010
public final class Formatter implements Closeable, Flushable {
2011+
// Caching DecimalFormatSymbols. Non-volatile to avoid thread slamming.
2012+
private static DecimalFormatSymbols DFS = null;
2013+
private static DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
2014+
// Capture local copy to avoid thread race.
2015+
DecimalFormatSymbols dfs = DFS;
2016+
if (dfs != null && dfs.getLocale().equals(locale)) {
2017+
return dfs;
2018+
}
2019+
// Fetch a new local instance of DecimalFormatSymbols. Note that DFS are mutable
2020+
// and this instance is reserved for Formatter.
2021+
dfs = DecimalFormatSymbols.getInstance(locale);
2022+
// Non-volatile here is acceptable heuristic.
2023+
DFS = dfs;
2024+
return dfs;
2025+
}
2026+
2027+
// Use zero from cached DecimalFormatSymbols.
2028+
private static char getZero(Locale locale) {
2029+
return locale == null ? '0' : getDecimalFormatSymbols(locale).getZeroDigit();
2030+
}
2031+
2032+
// Use decimal separator from cached DecimalFormatSymbols.
2033+
private static char getDecimalSeparator(Locale locale) {
2034+
return locale == null ? '.' : getDecimalFormatSymbols(locale).getDecimalSeparator();
2035+
}
2036+
2037+
// Use grouping separator from cached DecimalFormatSymbols.
2038+
private static char getGroupingSeparator(Locale locale) {
2039+
return locale == null ? ',' : getDecimalFormatSymbols(locale).getGroupingSeparator();
2040+
}
2041+
20112042
private Appendable a;
20122043
private final Locale l;
2013-
20142044
private IOException lastException;
20152045

2016-
// Non-character value used to mark zero as uninitialized
2017-
private static final char ZERO_SENTINEL = '\uFFFE';
2018-
private char zero = ZERO_SENTINEL;
2019-
20202046
/**
20212047
* Returns a charset object for the given charset name.
20222048
* @throws NullPointerException is csn is null
@@ -2523,20 +2549,6 @@ public Formatter(OutputStream os, Charset charset, Locale l) {
25232549
this(l, new BufferedWriter(new OutputStreamWriter(os, charset)));
25242550
}
25252551

2526-
private char zero() {
2527-
char zero = this.zero;
2528-
if (zero == ZERO_SENTINEL) {
2529-
if ((l != null) && !l.equals(Locale.US)) {
2530-
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
2531-
zero = dfs.getZeroDigit();
2532-
} else {
2533-
zero = '0';
2534-
}
2535-
this.zero = zero;
2536-
}
2537-
return zero;
2538-
}
2539-
25402552
/**
25412553
* Returns the locale set by the construction of this formatter.
25422554
*
@@ -4498,14 +4510,6 @@ private void failConversion(char c, Object arg) {
44984510
throw new IllegalFormatConversionException(c, arg.getClass());
44994511
}
45004512

4501-
private char getZero(Formatter fmt, Locale l) {
4502-
if ((l != null) && !l.equals(fmt.locale())) {
4503-
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
4504-
return dfs.getZeroDigit();
4505-
}
4506-
return fmt.zero();
4507-
}
4508-
45094513
private StringBuilder localizedMagnitude(Formatter fmt, StringBuilder sb,
45104514
long value, int flags, int width, Locale l) {
45114515
return localizedMagnitude(fmt, sb, Long.toString(value, 10), 0, flags, width, l);
@@ -4519,7 +4523,7 @@ private StringBuilder localizedMagnitude(Formatter fmt, StringBuilder sb,
45194523
}
45204524
int begin = sb.length();
45214525

4522-
char zero = getZero(fmt, l);
4526+
char zero = getZero(l);
45234527

45244528
// determine localized grouping separator and size
45254529
char grpSep = '\0';
@@ -4536,21 +4540,15 @@ private StringBuilder localizedMagnitude(Formatter fmt, StringBuilder sb,
45364540
}
45374541

45384542
if (dot < len) {
4539-
if (l == null || l.equals(Locale.US)) {
4540-
decSep = '.';
4541-
} else {
4542-
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
4543-
decSep = dfs.getDecimalSeparator();
4544-
}
4543+
decSep = getDecimalSeparator(l);
45454544
}
45464545

45474546
if (Flags.contains(f, Flags.GROUP)) {
4547+
grpSep = getGroupingSeparator(l);
4548+
45484549
if (l == null || l.equals(Locale.US)) {
4549-
grpSep = ',';
45504550
grpSize = 3;
45514551
} else {
4552-
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
4553-
grpSep = dfs.getGroupingSeparator();
45544552
DecimalFormat df = null;
45554553
NumberFormat nf = NumberFormat.getNumberInstance(l);
45564554
if (nf instanceof DecimalFormat) {
@@ -4567,7 +4565,7 @@ private StringBuilder localizedMagnitude(Formatter fmt, StringBuilder sb,
45674565
}
45684566
String[] all = adapter.getLocaleResources(l)
45694567
.getNumberPatterns();
4570-
df = new DecimalFormat(all[0], dfs);
4568+
df = new DecimalFormat(all[0], getDecimalFormatSymbols(l));
45714569
}
45724570
grpSize = df.getGroupingSize();
45734571
// Some locales do not use grouping (the number
@@ -4612,7 +4610,7 @@ private StringBuilder localizedMagnitude(Formatter fmt, StringBuilder sb,
46124610
// group separators is added for any locale.
46134611
private void localizedMagnitudeExp(Formatter fmt, StringBuilder sb, char[] value,
46144612
final int offset, Locale l) {
4615-
char zero = getZero(fmt, l);
4613+
char zero = getZero(l);
46164614

46174615
int len = value.length;
46184616
for (int j = offset; j < len; j++) {

‎test/jdk/java/text/Format/NumberFormat/IntlTestDecimalFormatSymbols.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -23,6 +23,7 @@
2323

2424
/*
2525
* @test
26+
* @bug 8282625
2627
* @library /java/text/testlib
2728
* @summary test International Decimal Format Symbols
2829
*/
@@ -60,6 +61,14 @@ public void TestSymbols()
6061

6162
// just do some VERY basic tests to make sure that get/set work
6263

64+
if (!fr.getLocale().equals(Locale.FRENCH)) {
65+
errln("ERROR: French DecimalFormatSymbols not Locale.FRENCH");
66+
}
67+
68+
if (!en.getLocale().equals(Locale.ENGLISH)) {
69+
errln("ERROR: English DecimalFormatSymbols not Locale.ENGLISH");
70+
}
71+
6372
char zero = en.getZeroDigit();
6473
fr.setZeroDigit(zero);
6574
if(fr.getZeroDigit() != en.getZeroDigit()) {

0 commit comments

Comments
 (0)
Please sign in to comment.