diff --git a/src/java.base/share/classes/java/text/ChoiceFormat.java b/src/java.base/share/classes/java/text/ChoiceFormat.java index d9f7557dded..b9aad971aa5 100644 --- a/src/java.base/share/classes/java/text/ChoiceFormat.java +++ b/src/java.base/share/classes/java/text/ChoiceFormat.java @@ -41,7 +41,9 @@ package java.text; import java.io.InvalidObjectException; import java.io.IOException; import java.io.ObjectInputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Objects; /** * {@code ChoiceFormat} is a concrete subclass of {@code NumberFormat} that @@ -238,6 +240,7 @@ public class ChoiceFormat extends NumberFormat { * @see #ChoiceFormat(String) */ public void applyPattern(String newPattern) { + Objects.requireNonNull(newPattern, "newPattern must not be null"); applyPatternImpl(newPattern); } @@ -249,86 +252,92 @@ public class ChoiceFormat extends NumberFormat { * further understanding of certain special characters: "#", "<", "\u2264", "|". */ private void applyPatternImpl(String newPattern) { - StringBuilder[] segments = new StringBuilder[2]; - for (int i = 0; i < segments.length; ++i) { - segments[i] = new StringBuilder(); - } - double[] newChoiceLimits = new double[30]; - String[] newChoiceFormats = new String[30]; - int count = 0; - int part = 0; // 0 denotes limit, 1 denotes format - double startValue = 0; - double oldStartValue = Double.NaN; + // Set up components + ArrayList limits = new ArrayList<>(); + ArrayList formats = new ArrayList<>(); + StringBuilder[] segments = new StringBuilder[]{new StringBuilder(), + new StringBuilder()}; + int part = 0; // 0 denotes LIMIT. 1 denotes FORMAT. + double limit = 0; boolean inQuote = false; + + // Parse the string, alternating the value of part for (int i = 0; i < newPattern.length(); ++i) { char ch = newPattern.charAt(i); - if (ch=='\'') { - // Check for "''" indicating a literal quote - if ((i+1) d).toArray(); + choiceFormats = formats.toArray(new String[0]); + } + + /** + * Converts a string value to its double representation; this is used + * to create the limit segment while applying a pattern. + * Handles "\u221E", as specified by the pattern syntax. + */ + private static double stringToNum(String str) { + return switch (str) { + case "\u221E" -> Double.POSITIVE_INFINITY; + case "-\u221E" -> Double.NEGATIVE_INFINITY; + default -> Double.parseDouble(str); + }; } /** @@ -402,6 +411,7 @@ public class ChoiceFormat extends NumberFormat { * @see #applyPattern */ public ChoiceFormat(String newPattern) { + Objects.requireNonNull(newPattern, "newPattern must not be null"); applyPatternImpl(newPattern); } @@ -574,6 +584,24 @@ public class ChoiceFormat extends NumberFormat { return Math.nextUp(d); } + /** + * Finds the least double greater than {@code d} (if {@code positive} is + * {@code true}), or the greatest double less than {@code d} (if + * {@code positive} is {@code false}). + * If {@code NaN}, returns same value. + * + * @implNote This is equivalent to calling + * {@code positive ? Math.nextUp(d) : Math.nextDown(d)} + * + * @param d the reference value + * @param positive {@code true} if the least double is desired; + * {@code false} otherwise + * @return the least or greater double value + */ + public static double nextDouble (double d, boolean positive) { + return positive ? Math.nextUp(d) : Math.nextDown(d); + } + /** * Finds the greatest double less than {@code d}. * If {@code NaN}, returns same value. @@ -593,8 +621,7 @@ public class ChoiceFormat extends NumberFormat { * Overrides Cloneable */ @Override - public Object clone() - { + public Object clone() { ChoiceFormat other = (ChoiceFormat) super.clone(); // for primitives or immutables, shallow clone is enough other.choiceLimits = choiceLimits.clone(); @@ -685,37 +712,4 @@ public class ChoiceFormat extends NumberFormat { * @serial */ private String[] choiceFormats; - - /** - * Finds the least double greater than {@code d} (if {@code positive} is - * {@code true}), or the greatest double less than {@code d} (if - * {@code positive} is {@code false}). - * If {@code NaN}, returns same value. - * - * @implNote This is equivalent to calling - * {@code positive ? Math.nextUp(d) : Math.nextDown(d)} - * - * @param d the reference value - * @param positive {@code true} if the least double is desired; - * {@code false} otherwise - * @return the least or greater double value - */ - public static double nextDouble (double d, boolean positive) { - return positive ? Math.nextUp(d) : Math.nextDown(d); - } - - private static double[] doubleArraySize(double[] array) { - int oldSize = array.length; - double[] newArray = new double[oldSize * 2]; - System.arraycopy(array, 0, newArray, 0, oldSize); - return newArray; - } - - private String[] doubleArraySize(String[] array) { - int oldSize = array.length; - String[] newArray = new String[oldSize * 2]; - System.arraycopy(array, 0, newArray, 0, oldSize); - return newArray; - } - } diff --git a/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java b/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java index 82cf256b70b..8fd8d42ef51 100644 --- a/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java +++ b/test/jdk/java/text/Format/ChoiceFormat/PatternsTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 6285888 6801704 + * @bug 6285888 6801704 8325898 * @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 @@ -31,14 +31,13 @@ * @run junit PatternsTest */ -import java.text.ChoiceFormat; - import org.junit.jupiter.api.Test; 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.assertDoesNotThrow; +import java.text.ChoiceFormat; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -97,6 +96,7 @@ public class PatternsTest { // an exception. private static Arguments[] invalidPatternsThrowsTest() { return new Arguments[] { + arguments("#", ERR1), // Only relation arguments("#foo", ERR1), // No Limit arguments("0#foo|#|1#bar", ERR1), // Missing Relation in SubPattern arguments("#|", ERR1), // Missing Limit @@ -127,11 +127,20 @@ public class PatternsTest { // after discarding occurs. private static Arguments[] invalidPatternsDiscardedTest() { return new Arguments[] { + // Incomplete SubPattern (limit only) at end of Pattern + arguments("1#bar|2", "1#bar"), // Incomplete SubPattern at the end of the Pattern arguments("0#foo|1#bar|baz", "0#foo|1#bar"), + // Incomplete SubPattern with trailing | at the end of the Pattern + // Prior to 6801704, it created the broken "0#foo|1#bar|1#" + // which caused formatting 1 to return an empty string + arguments("0#foo|1#bar|baz|", "0#foo|1#bar"), + // Same as previous, with additional incomplete subPatterns + arguments("0#foo|1#bar|baz|quux", "0#foo|1#bar"), // --- These throw an ArrayIndexOutOfBoundsException - // when attempting to format with them --- + // when attempting to format with them as the incomplete patterns + // are discarded, initializing the cFmt with empty limits and formats --- // SubPattern with only a Limit (which is interpreted as a Format) arguments("0", ""), // SubPattern with only a Format