diff --git a/src/java.base/share/classes/java/text/ChoiceFormat.java b/src/java.base/share/classes/java/text/ChoiceFormat.java index 1252efe33b5..d9f7557dded 100644 --- a/src/java.base/share/classes/java/text/ChoiceFormat.java +++ b/src/java.base/share/classes/java/text/ChoiceFormat.java @@ -68,71 +68,18 @@ * doesn't require any complex setup for a given locale. In fact, * {@code ChoiceFormat} doesn't implement any locale specific behavior. * - *
- * A {@code ChoiceFormat} can be constructed using either an array of formats - * and an array of limits or a string pattern. When constructing with - * format and limit arrays, the length of these arrays must be the same. - * - * For example, - *
- * Below is an example of constructing a ChoiceFormat with arrays to format - * and parse values: - * {@snippet lang=java : - * double[] limits = {1,2,3,4,5,6,7}; - * String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}; - * ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames); - * ParsePosition status = new ParsePosition(0); - * for (double i = 0.0; i <= 8.0; ++i) { - * status.setIndex(0); - * System.out.println(i + " -> " + form.format(i) + " -> " - * + form.parse(form.format(i),status)); - * } - * } - * - *
- * For more sophisticated patterns, {@code ChoiceFormat} can be used with - * {@link MessageFormat} to produce accurate forms for singular and plural: - * {@snippet lang=java : - * MessageFormat msgFmt = new MessageFormat("The disk \"{0}\" contains {1}."); - * double[] fileLimits = {0,1,2}; - * String[] filePart = {"no files","one file","{1,number} files"}; - * ChoiceFormat fileChoices = new ChoiceFormat(fileLimits, filePart); - * msgFmt.setFormatByArgumentIndex(1, fileChoices); - * Object[] args = {"MyDisk", 1273}; - * System.out.println(msgFmt.format(args)); - * } - * The output with different values for {@code fileCount}: - *
- * See {@link MessageFormat##pattern_caveats MessageFormat} for caveats regarding - * {@code MessageFormat} patterns within a {@code ChoiceFormat} pattern. - * *- * The disk "MyDisk" contains no files. - * The disk "MyDisk" contains one file. - * The disk "MyDisk" contains 1,273 files. - *
** * Note:The relation ≤ is not equivalent to <= * - **
* *- Pattern: *
- SubPattern *("|" SubPattern) - *
- Note: Each additional SubPattern must have a Limit greater than the previous SubPattern's Limit *
*
* *- SubPattern: *
- Limit Relation Format + *
- Note: Each additional SubPattern must have an ascending Limit-Relation interval
*@@ -172,20 +119,54 @@ * *
*
* *- Format: - *
- Any characters except the Relation symbols + *
- Any characters except the special pattern character '|' *
If a Relation symbol is to be used within a Format pattern, - * it must be single quoted. For example, - * {@code new ChoiceFormat("1# '#'1 ").format(1)} returns {@code " #1 "}. + *
To use a reserved special pattern character within a Format pattern, + * it must be single quoted. For example, {@code new ChoiceFormat("1#'|'foo'|'").format(1)} + * returns {@code "|foo|"}. * Use two single quotes in a row to produce a literal single quote. For example, * {@code new ChoiceFormat("1# ''one'' ").format(1)} returns {@code " 'one' "}. * - *
Below is an example of constructing a ChoiceFormat with a pattern: + *
+ * A {@code ChoiceFormat} can be constructed using either an array of formats + * and an array of limits or a string pattern. When constructing with + * format and limit arrays, the length of these arrays must be the same. + * + * For example, + *
+ * Below is an example of constructing a ChoiceFormat with arrays to format + * and parse values: + * {@snippet lang=java : + * double[] limits = {1,2,3,4,5,6,7}; + * String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}; + * ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames); + * ParsePosition status = new ParsePosition(0); + * for (double i = 0.0; i <= 8.0; ++i) { + * status.setIndex(0); + * System.out.println(i + " -> " + form.format(i) + " -> " + * + form.parse(form.format(i),status)); + * } + * } + * + *
Below is an example of constructing a ChoiceFormat with a String pattern:
* {@snippet lang=java :
* ChoiceFormat fmt = new ChoiceFormat(
* "-1#is negative| 0#is zero or fraction | 1#is one |1.0
@@ -254,7 +256,7 @@ private void applyPatternImpl(String newPattern) {
double[] newChoiceLimits = new double[30];
String[] newChoiceFormats = new String[30];
int count = 0;
- int part = 0;
+ int part = 0; // 0 denotes limit, 1 denotes format
double startValue = 0;
double oldStartValue = Double.NaN;
boolean inQuote = false;
@@ -270,7 +272,10 @@ private void applyPatternImpl(String newPattern) {
}
} else if (inQuote) {
segments[part].append(ch);
- } else if (ch == '<' || ch == '#' || ch == '\u2264') {
+ } else if (part == 0 && (ch == '<' || ch == '#' || ch == '\u2264')) {
+ // Only consider relational symbols if parsing the limit segment (part == 0).
+ // Don't treat a relational symbol as syntactically significant
+ // when parsing Format segment (part == 1)
if (segments[0].length() == 0) {
throw new IllegalArgumentException("Each interval must"
+ " contain a number before a format");
diff --git a/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java b/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java
index dc514f5459f..82cf256b70b 100644
--- a/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java
+++ b/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, 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,7 +23,7 @@
/*
* @test
- * @bug 6801704
+ * @bug 6285888 6801704
* @summary Test the expected behavior for a wide range of patterns (both
* correct and incorrect). This test documents the behavior of incorrect
* ChoiceFormat patterns either throwing an exception, or discarding
@@ -101,10 +101,13 @@ private static Arguments[] invalidPatternsThrowsTest() {
arguments("0#foo|#|1#bar", ERR1), // Missing Relation in SubPattern
arguments("#|", ERR1), // Missing Limit
arguments("##|", ERR1), // Double Relations
- arguments("0#foo1#", ERR1), // SubPattern not separated by '|'
- arguments("0#foo#", ERR1), // Using a Relation in a format
arguments("0#test|#", ERR1), // SubPattern missing Limit
arguments("0#foo|3#bar|1#baz", ERR2), // Non-ascending Limits
+
+ // No longer throw IAE after 6285888, as relational symbols
+ // can now be used within the Format segment.
+ // arguments("0#foo1#", ERR1), // SubPattern not separated by '|'
+ // arguments("0#foo#", ERR1), // Using a Relation in a format
};
}
diff --git a/test/jdk/java/text/Format/ChoiceFormat/SymbolsInFormatSegment.java b/test/jdk/java/text/Format/ChoiceFormat/SymbolsInFormatSegment.java
new file mode 100644
index 00000000000..678c7854e79
--- /dev/null
+++ b/test/jdk/java/text/Format/ChoiceFormat/SymbolsInFormatSegment.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6285888
+ * @summary Ensure ChoiceFormat supports "#", "<", "≤" within
+ * the format segment of a ChoiceFormat String pattern
+ * @run junit SymbolsInFormatSegment
+ */
+
+import java.text.ChoiceFormat;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/*
+ * These tests would previously throw IAEs on all input as ChoiceFormat would parse
+ * the relational symbol syntactically (when not needed). With the associated change
+ * set, ChoiceFormat knows to not treat any subsequent relational symbols as
+ * syntactically significant unless a '|' has been parsed.
+ */
+public class SymbolsInFormatSegment {
+
+ // Test a variety of patterns with relational symbols in the Format segment
+ @ParameterizedTest
+ @MethodSource("patternsWithSymbols")
+ public void allowInConstructor(String pattern, String expected, int limit) {
+ var cf = new ChoiceFormat(pattern);
+ assertEquals(expected, cf.format(limit));
+ }
+
+ // Same as previous test, but check the applyPattern method
+ @ParameterizedTest
+ @MethodSource("patternsWithSymbols")
+ public void allowInApplyPattern(String pattern, String expected, int limit) {
+ var cf = new ChoiceFormat("");
+ cf.applyPattern(pattern);
+ assertEquals(expected, cf.format(limit));
+ }
+
+ private static Stream
+ * See {@link MessageFormat##pattern_caveats MessageFormat} for caveats regarding
+ * {@code MessageFormat} patterns within a {@code ChoiceFormat} pattern.
+ *
*
+ * The disk "MyDisk" contains no files.
+ * The disk "MyDisk" contains one file.
+ * The disk "MyDisk" contains 1,273 files.
+ *
Synchronization
*
*