| /* | |
| * 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 8327640 8331485 8333755 8335668 | |
| * @summary Test suite for NumberFormat parsing with strict leniency | |
| * @run junit/othervm -Duser.language=en -Duser.country=US StrictParseTest | |
| * @run junit/othervm -Duser.language=ja -Duser.country=JP StrictParseTest | |
| * @run junit/othervm -Duser.language=zh -Duser.country=CN StrictParseTest | |
| * @run junit/othervm -Duser.language=tr -Duser.country=TR StrictParseTest | |
| * @run junit/othervm -Duser.language=de -Duser.country=DE StrictParseTest | |
| * @run junit/othervm -Duser.language=fr -Duser.country=FR StrictParseTest | |
| * @run junit/othervm -Duser.language=ar -Duser.country=AR StrictParseTest | |
| */ | |
| import org.junit.jupiter.api.BeforeEach; | |
| import org.junit.jupiter.api.Test; | |
| import org.junit.jupiter.api.condition.EnabledIfSystemProperty; | |
| import org.junit.jupiter.params.ParameterizedTest; | |
| import org.junit.jupiter.params.provider.Arguments; | |
| import org.junit.jupiter.params.provider.MethodSource; | |
| import java.text.CompactNumberFormat; | |
| import java.text.DecimalFormat; | |
| import java.text.DecimalFormatSymbols; | |
| import java.text.NumberFormat; | |
| import java.text.ParseException; | |
| import java.text.ParsePosition; | |
| import java.util.Locale; | |
| import java.util.stream.Stream; | |
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | |
| import static org.junit.jupiter.api.Assertions.assertEquals; | |
| import static org.junit.jupiter.api.Assertions.assertNull; | |
| import static org.junit.jupiter.api.Assertions.assertThrows; | |
| // Tests strict parsing, this is done by testing the NumberFormat factory instances | |
| // against a number of locales with different formatting conventions. The locales | |
| // used all use a grouping size of 3. | |
| public class StrictParseTest { | |
| // Used to retrieve the locale's expected symbols | |
| private static final DecimalFormatSymbols dfs = | |
| new DecimalFormatSymbols(Locale.getDefault()); | |
| // We re-use these formats for the respective factory tests | |
| private static final DecimalFormat dFmt = | |
| (DecimalFormat) NumberFormat.getNumberInstance(Locale.getDefault()); | |
| private static final DecimalFormat cFmt = | |
| (DecimalFormat) NumberFormat.getCurrencyInstance(Locale.getDefault()); | |
| private static final DecimalFormat pFmt = | |
| (DecimalFormat) NumberFormat.getPercentInstance(Locale.getDefault()); | |
| private static final CompactNumberFormat cmpctFmt = | |
| (CompactNumberFormat) NumberFormat.getCompactNumberInstance(Locale.getDefault(), | |
| NumberFormat.Style.SHORT); | |
| private static final NumberFormat[] FORMATS = new NumberFormat[]{dFmt, cFmt, pFmt, cmpctFmt}; | |
| // Restore defaults before runs | |
| @BeforeEach | |
| void beforeEach() { | |
| for (NumberFormat fmt : FORMATS) { | |
| fmt.setStrict(true); | |
| fmt.setParseIntegerOnly(false); | |
| fmt.setGroupingUsed(true); | |
| } | |
| // Grouping Size is not defined at NumberFormat level | |
| // Compact needs to manually init grouping size | |
| cmpctFmt.setGroupingSize(3); | |
| } | |
| // ---- NumberFormat tests ---- | |
| // Guarantee some edge case test input | |
| @Test // Non-localized, run once | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void uniqueCaseNumberFormatTest() { | |
| // Format with grouping size = 3, prefix = a, suffix = b | |
| DecimalFormat nonLocalizedDFmt = new DecimalFormat("a#,#00.00b"); | |
| nonLocalizedDFmt.setStrict(true); | |
| // Text after suffix | |
| failParse(nonLocalizedDFmt, "a12bfoo", 3); | |
| failParse(nonLocalizedDFmt, "a123,456.00bc", 11); | |
| // Text after prefix | |
| failParse(nonLocalizedDFmt, "ac123", 0); | |
| // Missing suffix | |
| failParse(nonLocalizedDFmt, "a123", 4); | |
| // Prefix contains a decimal separator | |
| failParse(nonLocalizedDFmt, ".a123", 0); | |
| // Test non grouping size of 3 | |
| nonLocalizedDFmt.setGroupingSize(1); | |
| successParse(nonLocalizedDFmt, "a1,2,3,4b"); | |
| failParse(nonLocalizedDFmt, "a1,2,3,45,6b", 8); | |
| nonLocalizedDFmt.setGroupingSize(5); | |
| successParse(nonLocalizedDFmt, "a12345,67890b"); | |
| successParse(nonLocalizedDFmt, "a1234,67890b"); | |
| failParse(nonLocalizedDFmt, "a123456,7890b", 6); | |
| } | |
| // 8333755: Check that parsing with integer only against a suffix value works | |
| @Test // Non-localized, run once | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void integerOnlyParseWithSuffixTest() { | |
| // Some pattern with a suffix | |
| DecimalFormat fmt = new DecimalFormat("0.00b"); | |
| fmt.setParseIntegerOnly(true); | |
| assertEquals(5d, successParse(fmt, "5.55b", 1)); | |
| assertEquals(5d, successParse(fmt, "5b", 2)); | |
| assertEquals(5555d, successParse(fmt, "5,555.55b", 5)); | |
| assertEquals(5d, successParse(fmt, "5.55E55b", 1)); | |
| } | |
| @Test // Non-localized, only run once | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void badExponentParseNumberFormatTest() { | |
| // Some fmt, with an "E" exponent string | |
| DecimalFormat fmt = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); | |
| fmt.setStrict(true); | |
| // Upon non-numeric in exponent, parse will exit early and suffix will not | |
| // exactly match, causing failure | |
| failParse(fmt, "1.23E45.1", 7); | |
| failParse(fmt, "1.23E45.", 7); | |
| failParse(fmt, "1.23E45FOO3222", 7); | |
| } | |
| // All input Strings should fail | |
| @ParameterizedTest | |
| @MethodSource("badParseStrings") | |
| public void numFmtFailParseTest(String toParse, int expectedErrorIndex) { | |
| failParse(dFmt, toParse, expectedErrorIndex); | |
| } | |
| // All input Strings should pass and return expected value. | |
| @ParameterizedTest | |
| @MethodSource("validParseStrings") | |
| public void numFmtSuccessParseTest(String toParse, double expectedValue) { | |
| assertEquals(expectedValue, successParse(dFmt, toParse)); | |
| } | |
| // All input Strings should fail | |
| @ParameterizedTest | |
| @MethodSource("negativeBadParseStrings") | |
| public void negNumFmtFailParseTest(String toParse, int expectedErrorIndex) { | |
| failParse(dFmt, toParse, expectedErrorIndex); | |
| } | |
| // All input Strings should pass and return expected value. | |
| @ParameterizedTest | |
| @MethodSource("negativeValidParseStrings") | |
| public void negNumFmtSuccessParseTest(String toParse, double expectedValue) { | |
| assertEquals(expectedValue, successParse(dFmt, toParse)); | |
| } | |
| // Exception should be thrown if grouping separator occurs anywhere | |
| // Don't pass badParseStrings as a data source, since they may fail for other reasons | |
| @ParameterizedTest | |
| @MethodSource({"validParseStrings", "noGroupingParseStrings"}) | |
| public void numFmtStrictGroupingNotUsed(String toParse) { | |
| // When grouping is not used, if a grouping separator is found, | |
| // a failure should occur | |
| dFmt.setGroupingUsed(false); | |
| int failIndex = toParse.indexOf( | |
| dFmt.getDecimalFormatSymbols().getGroupingSeparator()); | |
| if (failIndex > -1) { | |
| failParse(dFmt, toParse, failIndex); | |
| } else { | |
| successParse(dFmt, toParse); | |
| } | |
| } | |
| // 8333755: Parsing behavior should follow normal strict behavior | |
| // However the index returned, should be before decimal point | |
| // and the value parsed equal to the integer portion | |
| @ParameterizedTest | |
| @MethodSource("validIntegerOnlyParseStrings") | |
| public void numFmtStrictIntegerOnlyUsedTest(String toParse, Number expVal) { | |
| dFmt.setParseIntegerOnly(true); | |
| int expectedIndex = toParse.indexOf(dfs.getDecimalSeparator()); | |
| if (expectedIndex > -1) { | |
| assertEquals(successParse(dFmt, toParse, expectedIndex), expVal); | |
| } else { | |
| assertEquals(successParse(dFmt, toParse), expVal); | |
| } | |
| } | |
| // 8335668: Parsing with integer only against String with no integer portion | |
| // should fail, not return 0. Expected error index should be 0 | |
| @Test | |
| public void integerParseOnlyFractionOnlyTest() { | |
| var fmt = NumberFormat.getIntegerInstance(); | |
| failParse(fmt, localizeText("."), 0); | |
| failParse(fmt, localizeText(".0"), 0); | |
| failParse(fmt, localizeText(".55"), 0); | |
| } | |
| // 8335668: Parsing with integer only against String with no integer portion | |
| // should fail, not return 0. Expected error index should be 0 | |
| @Test // Non-localized, run once | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void compactIntegerParseOnlyFractionOnlyTest() { | |
| var fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); | |
| fmt.setParseIntegerOnly(true); | |
| failParse(fmt, ".K", 0); | |
| failParse(fmt, ".0K", 0); | |
| failParse(fmt, ".55K", 0); | |
| } | |
| // 8333755: Parsing behavior should follow normal strict behavior | |
| // when it comes to failures. | |
| @ParameterizedTest | |
| @MethodSource("badParseStrings") | |
| public void numFmtStrictIntegerOnlyUsedFailTest(String toParse, int expectedErrorIndex) { | |
| dFmt.setParseIntegerOnly(true); | |
| failParse(dFmt, toParse, expectedErrorIndex); | |
| } | |
| // ---- CurrencyFormat tests ---- | |
| @ParameterizedTest | |
| @MethodSource("currencyBadParseStrings") | |
| public void currFmtFailParseTest(String toParse, int expectedErrorIndex) { | |
| failParse(cFmt, toParse, expectedErrorIndex); | |
| } | |
| @ParameterizedTest | |
| @MethodSource("currencyValidParseStrings") | |
| public void currFmtSuccessParseTest(String toParse, double expectedValue) { | |
| assertEquals(expectedValue, successParse(cFmt, toParse)); | |
| } | |
| // ---- PercentFormat tests ---- | |
| @ParameterizedTest | |
| @MethodSource("percentBadParseStrings") | |
| public void percentFmtFailParseTest(String toParse, int expectedErrorIndex) { | |
| failParse(pFmt, toParse, expectedErrorIndex); | |
| } | |
| @ParameterizedTest | |
| @MethodSource("percentValidParseStrings") | |
| public void percentFmtSuccessParseTest(String toParse, double expectedValue) { | |
| assertEquals(expectedValue, successParse(pFmt, toParse)); | |
| } | |
| // ---- CompactNumberFormat tests ---- | |
| // Can match to both the decimalFormat patterns and the compact patterns | |
| // Thus we test leniency for both. Unlike the other tests, this test | |
| // is only ran against the US Locale and tests against data built with the | |
| // thousands format (K). | |
| @ParameterizedTest | |
| @MethodSource("compactBadParseStrings") | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void compactFmtFailParseTest(String toParse, int expectedErrorIndex) { | |
| failParse(cmpctFmt, toParse, expectedErrorIndex); | |
| } | |
| @ParameterizedTest | |
| @MethodSource("compactValidParseStrings") | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void compactFmtSuccessParseTest(String toParse, double expectedValue) { | |
| assertEquals(expectedValue, successParse(cmpctFmt, toParse)); | |
| } | |
| // Checks some odd leniency edge cases between matching of default pattern | |
| // and compact pattern. | |
| @Test // Non-localized, run once | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void compactFmtEdgeParseTest() { | |
| // Uses a compact format with unique and non-empty prefix/suffix for both | |
| // default and compact patterns | |
| CompactNumberFormat cnf = new CompactNumberFormat("a##0.0#b", DecimalFormatSymbols | |
| .getInstance(Locale.US), new String[]{"", "c0d"}); | |
| cnf.setStrict(true); | |
| // Existing behavior of failed prefix parsing has errorIndex return | |
| // the beginning of prefix, even if the error occurred later in the prefix. | |
| // Prefix empty | |
| failParse(cnf, "12345d", 0); | |
| failParse(cnf, "1b", 0); | |
| // Prefix bad | |
| failParse(cnf, "aa1d", 0); | |
| failParse(cnf, "cc1d", 0); | |
| failParse(cnf, "aa1b", 0); | |
| failParse(cnf, "cc1b", 0); | |
| // Suffix error index is always the start of the failed suffix | |
| // not necessarily where the error occurred in the suffix. This is | |
| // consistent with the prefix error index behavior. | |
| // Suffix empty | |
| failParse(cnf, "a1", 2); | |
| failParse(cnf, "c1", 2); | |
| // Suffix bad | |
| failParse(cnf, "a1dd", 2); | |
| failParse(cnf, "c1dd", 2); | |
| failParse(cnf, "a1bb", 2); | |
| failParse(cnf, "c1bb", 2); | |
| } | |
| @ParameterizedTest | |
| @MethodSource({"validIntegerOnlyParseStrings", "compactValidIntegerOnlyParseStrings"}) | |
| @EnabledIfSystemProperty(named = "user.language", matches = "en") | |
| public void compactFmtSuccessParseIntOnlyTest(String toParse, double expectedValue) { | |
| // compact does not accept exponents | |
| if (toParse.indexOf('E') > -1) { | |
| return; | |
| } | |
| cmpctFmt.setParseIntegerOnly(true); | |
| assertEquals(expectedValue, successParse(cmpctFmt, toParse, toParse.length())); | |
| } | |
| // Ensure that on failure, the original index of the PP remains the same | |
| @Test | |
| public void parsePositionIndexTest() { | |
| failParse(dFmt, localizeText("123,456,,789.00"), 8, 4); | |
| } | |
| // ---- Helper test methods ---- | |
| // Should parse entire String successfully, and return correctly parsed value. | |
| private Number successParse(NumberFormat fmt, String toParse) { | |
| return successParse(fmt, toParse, toParse.length()); | |
| } | |
| // Overloaded method that allows for an expected ParsePosition index value | |
| // that is not the string length. | |
| private Number successParse(NumberFormat fmt, String toParse, int expectedIndex) { | |
| // For Strings that don't have grouping separators, we test them with | |
| // grouping off so that they do not fail under the expectation that | |
| // grouping symbols should occur | |
| if (!toParse.contains(String.valueOf(dfs.getGroupingSeparator())) && | |
| !toParse.contains(String.valueOf(dfs.getMonetaryGroupingSeparator()))) { | |
| fmt.setGroupingUsed(false); | |
| } | |
| Number parsedValue = assertDoesNotThrow(() -> fmt.parse(toParse)); | |
| ParsePosition pp = new ParsePosition(0); | |
| assertDoesNotThrow(() -> fmt.parse(toParse, pp)); | |
| assertEquals(-1, pp.getErrorIndex(), | |
| "ParsePosition ErrorIndex is not in correct location"); | |
| assertEquals(expectedIndex, pp.getIndex(), | |
| "ParsePosition Index is not in correct location"); | |
| fmt.setGroupingUsed(true); | |
| return parsedValue.doubleValue(); | |
| } | |
| // Method which tests a parsing failure. Either a ParseException is thrown, | |
| // or null is returned depending on which parse method is invoked. When failing, | |
| // index should remain the initial index set to the ParsePosition while | |
| // errorIndex is the index of failure. | |
| private void failParse(NumberFormat fmt, String toParse, int expectedErrorIndex) { | |
| failParse(fmt, toParse, expectedErrorIndex, 0); | |
| } | |
| // Variant to check non 0 initial parse index | |
| private void failParse(NumberFormat fmt, String toParse, | |
| int expectedErrorIndex, int initialParseIndex) { | |
| ParsePosition pp = new ParsePosition(initialParseIndex); | |
| assertThrows(ParseException.class, () -> fmt.parse(toParse)); | |
| assertNull(fmt.parse(toParse, pp)); | |
| assertEquals(expectedErrorIndex, pp.getErrorIndex()); | |
| assertEquals(initialParseIndex, pp.getIndex()); | |
| } | |
| // ---- Data Providers ---- | |
| // These data providers use US locale grouping and decimal separators | |
| // for readability, however, the data is tested against multiple locales | |
| // and is converted appropriately at runtime. | |
| // Strings that should fail when parsed with strict leniency. | |
| // Given as Arguments<String, expectedErrorIndex> | |
| private static Stream<Arguments> badParseStrings() { | |
| return Stream.of( | |
| // Grouping symbol focus | |
| // Grouping symbol right before decimal | |
| Arguments.of("1,.", 2), | |
| Arguments.of("1,.1", 2), | |
| // Does not end with proper grouping size | |
| Arguments.of("1,1", 2), | |
| Arguments.of("1,11", 3), | |
| Arguments.of("1,1111", 5), | |
| Arguments.of("11,111,11", 8), | |
| // Does not end with proper grouping size (with decimal) | |
| Arguments.of("1,1.", 3), | |
| Arguments.of("1,11.", 4), | |
| Arguments.of("1,1111.", 5), | |
| Arguments.of("11,111,11.", 9), | |
| // Ends on a grouping symbol | |
| // Suffix matches correctly, so failure is on the "," | |
| Arguments.of("11,111,", 6), | |
| Arguments.of("11,", 2), | |
| Arguments.of("11,,", 3), | |
| // Ends with grouping symbol. Failure should occur on grouping, | |
| // even if non recognized char after | |
| Arguments.of("11,a", 2), | |
| // Improper grouping size (with decimal and digits after) | |
| Arguments.of("1,1.1", 3), | |
| Arguments.of("1,11.1", 4), | |
| Arguments.of("1,1111.1", 5), | |
| Arguments.of("11,111,11.1", 9), | |
| // Subsequent grouping symbols | |
| Arguments.of("1,,1", 2), | |
| Arguments.of("1,1,,1", 3), | |
| Arguments.of("1,,1,1", 2), | |
| // Invalid grouping sizes | |
| Arguments.of("1,11,111", 4), | |
| Arguments.of("11,11,111", 5), | |
| Arguments.of("111,11,11", 6), | |
| // First group is too large | |
| Arguments.of("1111,11,111", 3), | |
| Arguments.of("00000,11,111", 3), | |
| Arguments.of("111,1111111111", 7), | |
| Arguments.of("111,11", 5), | |
| Arguments.of("111,1111111111.", 7), | |
| Arguments.of("111,11.", 6), | |
| Arguments.of("111,1111111111.", 7), | |
| // Starts with grouping symbol | |
| Arguments.of(",111,,1,1", 0), | |
| Arguments.of(",1", 0), | |
| Arguments.of(",,1", 0), | |
| // Leading Zeros (not digits) | |
| Arguments.of("000,1,1", 5), | |
| Arguments.of("000,111,11,,1", 10), | |
| Arguments.of("0,000,1,,1,1", 7), | |
| // Bad suffix | |
| Arguments.of("1a", 1), | |
| // Bad chars in numerical portion | |
| Arguments.of("123a4", 3), | |
| Arguments.of("123.4a5", 5), | |
| // Variety of edge cases | |
| Arguments.of("123,456.77a", 10), | |
| Arguments.of("1,234a", 5), | |
| Arguments.of("1,.a", 2), | |
| Arguments.of("1.a", 2), | |
| Arguments.of("1.22a", 4), | |
| Arguments.of("1.1a1", 3), | |
| Arguments.of("1,234,a", 5), | |
| // Double decimal | |
| Arguments.of("1,234..5", 5)) | |
| .map(args -> Arguments.of( | |
| localizeText(String.valueOf(args.get()[0])), args.get()[1])); | |
| } | |
| // Strings that should parse fully. (Both in lenient and strict) | |
| // Given as Arguments<String, expectedParsedNumber> | |
| private static Stream<Arguments> validParseStrings() { | |
| return Stream.of( | |
| Arguments.of("1,234.55", 1234.55d), | |
| Arguments.of("1,234.5", 1234.5d), | |
| Arguments.of("1,234.00", 1234d), | |
| Arguments.of("1,234.0", 1234d), | |
| Arguments.of("1,234.", 1234d), | |
| Arguments.of("1", 1d), | |
| Arguments.of("10", 10d), | |
| Arguments.of("100", 100d), | |
| Arguments.of("1000", 1000d), | |
| Arguments.of("1,000", 1000d), | |
| Arguments.of("10,000", 10000d), | |
| Arguments.of("10000", 10000d), | |
| Arguments.of("100,000", 100000d), | |
| Arguments.of("1,000,000", 1000000d), | |
| Arguments.of("10,000,000", 10000000d), | |
| // Smaller value cases (w/ decimal) | |
| Arguments.of(".1", .1d), | |
| Arguments.of("1.1", 1.1d), | |
| Arguments.of("11.1", 11.1d)) | |
| .map(args -> Arguments.of( | |
| localizeText(String.valueOf(args.get()[0])), args.get()[1])); | |
| } | |
| // Separate test data set for integer only. | |
| // Valid parse strings, that would parse successfully for integer/non-integer parse | |
| private static Stream<Arguments> validIntegerOnlyParseStrings() { | |
| return Stream.of( | |
| Arguments.of("234", 234d), | |
| Arguments.of("234.", 234d), | |
| Arguments.of("234.1", 234d), | |
| Arguments.of("1,234.1", 1234d), | |
| Arguments.of("234.12345", 234d), | |
| Arguments.of("234.543E23", 234d), | |
| Arguments.of("234,000.55E22", 234000d), | |
| Arguments.of("234E22", 234E22)) | |
| .map(args -> Arguments.of(localizeText(String.valueOf(args.get()[0])), args.get()[1])); | |
| } | |
| // Separate test data set for no grouping. Can not use "badParseStrings", as | |
| // there is test data where the failure may occur from some other issue, | |
| // not related to grouping | |
| private static Stream<Arguments> noGroupingParseStrings() { | |
| return Stream.of( | |
| Arguments.of("12,34.a"), | |
| Arguments.of("123,.a1"), | |
| Arguments.of(",1234"), | |
| Arguments.of("123,")) | |
| .map(args -> Arguments.of(localizeText(String.valueOf(args.get()[0])))); | |
| } | |
| // Negative variant of a numerical format | |
| private static Stream<Arguments> negativeBadParseStrings() { | |
| return badParseStrings().map(args -> Arguments.of( | |
| dFmt.getNegativePrefix() + args.get()[0] + dFmt.getNegativeSuffix(), | |
| (int)args.get()[1] + dFmt.getNegativePrefix().length()) | |
| ); | |
| } | |
| // Negative variant of a numerical format | |
| private static Stream<Arguments> negativeValidParseStrings() { | |
| return validParseStrings().map(args -> Arguments.of( | |
| dFmt.getNegativePrefix() + args.get()[0] + dFmt.getNegativeSuffix(), | |
| (double) args.get()[1] * -1) | |
| ); | |
| } | |
| // Same as original with a percent prefix/suffix. | |
| // Additionally, increment expected error index if a prefix is added | |
| private static Stream<Arguments> percentBadParseStrings() { | |
| return badParseStrings().map(args -> Arguments.of( | |
| pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(), | |
| (int)args.get()[1] + pFmt.getPositivePrefix().length()) | |
| ); | |
| } | |
| // Expected parsed value should be / 100 as it is a percent format. | |
| private static Stream<Arguments> percentValidParseStrings() { | |
| return validParseStrings().map(args -> Arguments.of( | |
| pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(), | |
| (double)args.get()[1] / 100.0) | |
| ); | |
| } | |
| // Same as original with a currency prefix/suffix, but replace separators | |
| // with monetary variants. Additionally, increment expected error index | |
| // if a prefix is added | |
| private static Stream<Arguments> currencyBadParseStrings() { | |
| return badParseStrings().map(args -> Arguments.of( | |
| cFmt.getPositivePrefix() + String.valueOf(args.get()[0]) | |
| .replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator()) | |
| .replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator()) | |
| + cFmt.getPositiveSuffix(), | |
| (int)args.get()[1] + cFmt.getPositivePrefix().length()) | |
| ); | |
| } | |
| private static Stream<Arguments> currencyValidParseStrings() { | |
| return validParseStrings().map(args -> Arguments.of( | |
| cFmt.getPositivePrefix() + String.valueOf(args.get()[0]) | |
| .replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator()) | |
| .replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator()) | |
| + cFmt.getPositiveSuffix(), | |
| args.get()[1]) | |
| ); | |
| } | |
| // Compact Pattern Data Provider provides test input for both DecimalFormat patterns | |
| // and the compact patterns. As there is no method to retrieve compact patterns, | |
| // thus test only against US English locale, and use a hard coded K - 1000 | |
| private static Stream<Arguments> compactBadParseStrings() { | |
| return Stream.concat( | |
| badParseStrings().map(args -> Arguments.of(args.get()[0], args.get()[1])), | |
| badParseStrings().map(args -> Arguments.of(args.get()[0] + "K", args.get()[1])) | |
| ); | |
| } | |
| private static Stream<Arguments> compactValidParseStrings() { | |
| return Stream.concat( | |
| validParseStrings().map(args -> Arguments.of( | |
| args.get()[0], args.get()[1])), | |
| validParseStrings().map(args -> Arguments.of( | |
| args.get()[0] + "K", (double) args.get()[1] * 1000)) | |
| ); | |
| } | |
| private static Stream<Arguments> compactValidIntegerOnlyParseStrings() { | |
| return validIntegerOnlyParseStrings().map(args -> Arguments.of( | |
| args.get()[0] + "K", (double) args.get()[1] * 1000) | |
| ); | |
| } | |
| // Replace the grouping and decimal separators with localized variants | |
| // Used during localization of data | |
| private static String localizeText(String text) { | |
| // As this is a single pass conversion, this is safe for multiple replacement, | |
| // even if a ',' could be a decimal separator for a locale. | |
| StringBuilder sb = new StringBuilder(); | |
| for (int i = 0; i < text.length(); i++) { | |
| char c = text.charAt(i); | |
| if (c == ',') { | |
| sb.append(dfs.getGroupingSeparator()); | |
| } else if (c == '.') { | |
| sb.append(dfs.getDecimalSeparator()); | |
| } else if (c == 'E') { | |
| sb.append(dfs.getExponentSeparator()); | |
| } | |
| else if (c == '0') { | |
| sb.append(dfs.getZeroDigit()); | |
| } else { | |
| sb.append(c); | |
| } | |
| } | |
| return sb.toString(); | |
| } | |
| } |