6285888: ChoiceFormat can support unescaped relational symbols in the Format segment
Reviewed-by: naoto
This commit is contained in:
parent
144a08ee50
commit
d3c3194ac3
src/java.base/share/classes/java/text
test/jdk/java/text/Format/ChoiceFormat
@ -68,71 +68,18 @@ import java.util.Arrays;
|
||||
* doesn't require any complex setup for a given locale. In fact,
|
||||
* {@code ChoiceFormat} doesn't implement any locale specific behavior.
|
||||
*
|
||||
* <p>
|
||||
* 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,
|
||||
* <ul>
|
||||
* <li>
|
||||
* <em>limits</em> = {1,2,3,4,5,6,7}<br>
|
||||
* <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
|
||||
* <li>
|
||||
* <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
|
||||
* <em>formats</em> = {"no files", "one file", "many files"}<br>
|
||||
* ({@code nextDouble} can be used to get the next higher double, to
|
||||
* make the half-open interval.)
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* 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));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* 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}:
|
||||
* <blockquote><pre>
|
||||
* The disk "MyDisk" contains no files.
|
||||
* The disk "MyDisk" contains one file.
|
||||
* The disk "MyDisk" contains 1,273 files.
|
||||
* </pre></blockquote>
|
||||
* See {@link MessageFormat##pattern_caveats MessageFormat} for caveats regarding
|
||||
* {@code MessageFormat} patterns within a {@code ChoiceFormat} pattern.
|
||||
*
|
||||
* <h2><a id="patterns">Patterns</a></h2>
|
||||
* A {@code ChoiceFormat} pattern has the following syntax:
|
||||
* <blockquote>
|
||||
* <dl>
|
||||
* <dt><i>Pattern:</i>
|
||||
* <dd>SubPattern *("|" SubPattern)
|
||||
* <dd><i>Note: Each additional SubPattern must have a Limit greater than the previous SubPattern's Limit</i>
|
||||
* </dl>
|
||||
*
|
||||
* <dl>
|
||||
* <dt><i>SubPattern:</i>
|
||||
* <dd>Limit Relation Format
|
||||
* <dd><sub>Note: Each additional SubPattern must have an ascending Limit-Relation interval</sub></dd>
|
||||
* </dl>
|
||||
*
|
||||
* <dl>
|
||||
@ -172,20 +119,54 @@ import java.util.Arrays;
|
||||
*
|
||||
* <dl>
|
||||
* <dt><i>Format:</i>
|
||||
* <dd>Any characters except the <i>Relation</i> symbols
|
||||
* <dd>Any characters except the special pattern character '|'
|
||||
* </dl>
|
||||
*
|
||||
* </blockquote>
|
||||
*
|
||||
* <i>Note:The relation ≤ is not equivalent to <=</i>
|
||||
*
|
||||
* <p>If a <i>Relation</i> symbol is to be used within a <i>Format</i> pattern,
|
||||
* it must be single quoted. For example,
|
||||
* {@code new ChoiceFormat("1# '#'1 ").format(1)} returns {@code " #1 "}.
|
||||
* <p> To use a reserved special pattern character within a <i>Format</i> 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' "}.
|
||||
*
|
||||
* <p>Below is an example of constructing a ChoiceFormat with a pattern:
|
||||
* <h2>Usage Information</h2>
|
||||
*
|
||||
* <p>
|
||||
* 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,
|
||||
* <ul>
|
||||
* <li>
|
||||
* <em>limits</em> = {1,2,3,4,5,6,7}<br>
|
||||
* <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
|
||||
* <li>
|
||||
* <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
|
||||
* <em>formats</em> = {"no files", "one file", "many files"}<br>
|
||||
* ({@code nextDouble} can be used to get the next higher double, to
|
||||
* make the half-open interval.)
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* 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));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* <p>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<is 1+ |2#is two |2<is more than 2.");
|
||||
@ -202,6 +183,27 @@ import java.util.Arrays;
|
||||
* System.out.println(fmt.format(Double.POSITIVE_INFINITY)); // outputs "is more than 2."
|
||||
* }
|
||||
*
|
||||
* <p>
|
||||
* 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}:
|
||||
* <blockquote><pre>
|
||||
* The disk "MyDisk" contains no files.
|
||||
* The disk "MyDisk" contains one file.
|
||||
* The disk "MyDisk" contains 1,273 files.
|
||||
* </pre></blockquote>
|
||||
* See {@link MessageFormat##pattern_caveats MessageFormat} for caveats regarding
|
||||
* {@code MessageFormat} patterns within a {@code ChoiceFormat} pattern.
|
||||
*
|
||||
* <h2><a id="synchronization">Synchronization</a></h2>
|
||||
*
|
||||
* <p>
|
||||
@ -254,7 +256,7 @@ public class ChoiceFormat extends NumberFormat {
|
||||
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 @@ public class ChoiceFormat extends NumberFormat {
|
||||
}
|
||||
} 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");
|
||||
|
@ -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 @@ public class PatternsTest {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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<Arguments> patternsWithSymbols() {
|
||||
return Stream.of(
|
||||
// CSR example
|
||||
Arguments.of("1#The code is #7281", "The code is #7281", 1),
|
||||
// Other examples
|
||||
Arguments.of("1#<", "<", 1),
|
||||
Arguments.of("1#foo<", "foo<", 1),
|
||||
Arguments.of("1<foo\u2264", "foo\u2264", 1),
|
||||
Arguments.of("1\u2264foo#", "foo#", 1),
|
||||
Arguments.of("1#foo<#\u2264|2\u2264baz<#\u2264", "baz<#\u2264", 100),
|
||||
Arguments.of("1#foo<#\u2264|2#baz<#\u2264|3<bar##\u2264", "bar##\u2264", 100),
|
||||
Arguments.of("1#foo<#\u2264|2#baz<#\u2264|3\u2264bar##\u2264", "bar##\u2264", 100)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user