undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,180 @@
/*
* Copyright (c) 2003, 2023, 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 4018937
* @summary Confirm that DecimalFormat.parse() parses BigDecimal and BigInteger
* string values as expected. Specifically, ensure a ParseException is
* not thrown as well as the parsed value being numerically correct.
* Tests large String values with combinations of multipliers and exponents.
* @run junit BigDecimalCompatibilityTest
*/
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Locale;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
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;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class BigDecimalCompatibilityTest {
private static DecimalFormat df = new DecimalFormat();
// Save JVM default Locale
private static final Locale savedLocale = Locale.getDefault();
// ---- Used for the test data (start) ----
// Both ArrayList composed of Arguments(String longString, int multiplier)
private static final ArrayList<Arguments> bigIntegers = new ArrayList<Arguments>();
private static final ArrayList<Arguments> bigDecimals = new ArrayList<Arguments>();
// Long string data to generate combinations of test values
private static final String[] inputData = {
"0".repeat(400),
"1234567890".repeat(40)};
// Variety of exponents to test parse() against
private static final String[] exponents = {
"E-100", "E100", "E-900", "E900", ""
};
// Variety of multipliers that DecimalFormat can apply
private static final int[] multipliers = {
-1, 1, -100, 100, -9999, 9999
};
// ---- Used for the test data (end) ----
// Set JVM default Locale to US and populate the test arrayLists
@BeforeAll
static void initAll() {
Locale.setDefault(Locale.US);
buildTestData();
}
/*
* Uses inputData and exponents to build long string
* decimal and integer values and populate bigDecimals and bigIntegers
* accordingly. Attaches a multiplier value as well to the test data.
*/
private static void buildTestData() {
for (String longString1 : inputData) {
for (String longString2 : inputData) {
String bigInteger = longString1 + longString2;
for (int multiplier : multipliers) {
bigIntegers.add(Arguments.of(bigInteger, multiplier));
bigIntegers.add(Arguments.of('-' + bigInteger, multiplier));
}
for (String longString3 : inputData) {
for (String longString4 : inputData) {
for (String exponent : exponents) {
String bigDecimal = longString1 + longString2 + '.'
+ longString3 + longString4 + exponent;
for (int multiplier : multipliers) {
bigDecimals.add(Arguments.of(bigDecimal, multiplier));
bigDecimals.add(Arguments.of('-' + bigDecimal, multiplier));
}
}
}
}
}
}
}
// Restore JVM default Locale
@AfterAll
static void tearDownAll() {
Locale.setDefault(savedLocale);
}
// Tests strings with length 1600+. See test() for specific details.
@ParameterizedTest
@MethodSource("bigDecimalProvider")
public void bigDecimalParseTest(String longString, int multiplier) {
test(longString, multiplier);
}
// Returns 960 arrangements of bigDecimal string values and multipliers
// In the form of (String, int).
private static Stream<Arguments> bigDecimalProvider() {
return bigDecimals.stream();
}
// Tests strings with length 800+. See test() for specific details.
@ParameterizedTest
@MethodSource("bigIntegerProvider")
public void bigIntegerParseTest(String longString, int multiplier) {
test(longString, multiplier);
}
// Returns 48 arrangements of bigInteger string values and multipliers
// In the form of (String, int).
private static Stream<Arguments> bigIntegerProvider() {
return bigIntegers.stream();
}
/*
* Tests that parsing a large BigDecimal/BigInteger string value
* will not throw a ParseException with setParseBigDecimal as true.
* Parses with a variety of multiplier values. Then ensures that the parsed
* value is the expected number.
*/
private static void test(String longString, int multiplier) {
// Reset DecimalFormat for a clean test
df = new DecimalFormat();
df.setParseBigDecimal(true);
// wide enough to support the long string test data
df.setMaximumFractionDigits(Integer.MAX_VALUE);
df.setMultiplier(multiplier);
// Check parse and returned value. This was originally intended to ensure
// a ParseException is not thrown
Number parsedValue = assertDoesNotThrow(()-> df.parse(longString),
"Should not throw an Exception");
BigDecimal expectedValue = getExpected(longString, multiplier);
assertEquals(expectedValue, parsedValue, "With multiplier: " + multiplier);
}
// Utility to get a numerically correct value of a long string.
// Dependent on BigDecimal implementation
private static BigDecimal getExpected(String longString, int multiplier) {
BigDecimal expected = new BigDecimal(longString);
try {
expected = expected.divide(new BigDecimal(multiplier));
}
catch (ArithmeticException e) {
expected = expected.divide(new BigDecimal(multiplier), RoundingMode.HALF_EVEN);
}
return expected;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,725 @@
/*
* Copyright (c) 2003, 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 4018937 8008577 8174269 8333755
* @summary Confirm that methods which are newly added to support BigDecimal and BigInteger work as expected.
* @run junit/othervm BigDecimalParse
*/
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import static org.junit.jupiter.api.Assertions.fail;
public class BigDecimalParse {
// Change JVM default Locale
@BeforeAll
static void initAll() {
Locale.setDefault(Locale.forLanguageTag("en-US-u-cf-account"));
}
static final String nonsep_int =
"123456789012345678901234567890123456789012345678901234567890" +
"123456789012345678901234567890123456789012345678901234567890" +
"123456789012345678901234567890123456789012345678901234567890" +
"123456789012345678901234567890123456789012345678901234567890" +
"123456789012345678901234567890123456789012345678901234567890" +
"123456789012345678901234567890123456789012345678901234567890";
static final String sep_int =
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890," +
"123,456,789,012,345,678,901,234,567,890";
static final String nonsep_zero =
"000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000000000000000";
static final String sep_zero =
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000," +
"000,000,000,000,000,000,000,000,000,000";
static final String fra =
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789" +
"012345678901234567890123456789012345678901234567890123456789";
Number parsed = null;
ParsePosition pp;
boolean exceptionOccurred;
String msg;
DecimalFormat df;
/**
* Test for normal big numbers which have the fraction part
*/
@Test
public void test_Parse_in_DecimalFormat_BigDecimal() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
// From: 1234...7890.012...789
// To: BigDecimal 1234...7890.012...789
check(nonsep_int + "." + fra, new BigDecimal(nonsep_int + "." + fra));
// From: -1,234...7,890.012...789
// To: BigDecimal -1234...7890.012...789
check("-" + sep_int + "." + fra,
new BigDecimal("-" + nonsep_int + "." + fra));
// From: 000...0000.0...0
// To: BigDecimal 0E-360
check(nonsep_zero + "." + nonsep_zero,
new BigDecimal(nonsep_zero + "." + nonsep_zero));
// From: 0.000...0000123...789E370
// To: BigDecimal 0.0123...789
check("0.0000000000" + nonsep_zero + fra + "E370",
new BigDecimal("0.0000000000" + nonsep_zero + fra + "E370"));
// From: 0.1123...890E-360
// To: BigDecimal 1.123...890E-361
check("0.1" + nonsep_int + "E-360",
new BigDecimal("0.1" + nonsep_int + "E-360"));
// From: 000...0000.0...0123...7890
// To: BigDecimal 1.234...890E-361
check(nonsep_zero + "." + nonsep_zero + nonsep_int,
new BigDecimal(nonsep_zero + "." + nonsep_zero + nonsep_int));
// From: 0.123...890E360
// To: BigDecimal 123...890
check("0." + nonsep_int + "E360",
new BigDecimal("0." + nonsep_int + "E360"));
}
/**
* Test for normal big numbers which have the fraction part with multiplier
*/
@Test
public void test_Parse_in_DecimalFormat_BigDecimal_usingMultiplier() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
// From: 250,0...0,000.000...000
// To: 1000...0000.000...000
df.setMultiplier(250000000);
check("250,000,000," + sep_zero + "." + nonsep_zero,
new BigDecimal("1" + nonsep_zero + "." + nonsep_zero));
// From: -250,0...0,000.000...000
// To: -1000...0000.000...000
check("-250,000,000," + sep_zero + "." + nonsep_zero,
new BigDecimal("-1" + nonsep_zero + "." + nonsep_zero));
// From: 250,0...0,000.000...000
// To: -1000...0000.000...000
df.setMultiplier(-250000000);
check("250,000,000," + sep_zero + "." + nonsep_zero,
new BigDecimal("-1" + nonsep_zero + "." + nonsep_zero));
// From: -250,0...0,000.000...000
// To: 1000...0000.000...000
check("-250,000,000," + sep_zero + "." + nonsep_zero,
new BigDecimal("1" + nonsep_zero + "." + nonsep_zero));
// Confirm that ArithmeticException is handled properly
// From: 1000.000
// To: 333.333
df.setMultiplier(3);
check("1000.000", new BigDecimal("333.333"));
// Confirm that ArithmeticException is handled properly
// From: 10000.0000
// To: 303.0303
df.setMultiplier(33);
check("10000.0000", new BigDecimal("303.0303"));
}
/**
* Test for division by zero (BigDecimal)
*/
@Test
public void test_Parse_in_DecimalFormat_BigDecimal_DivisionByZero() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
df.setMultiplier(0);
// From: 1000.000
// To: Double.POSITIVE_INFINITY
check("1000.000", Double.POSITIVE_INFINITY);
// From: -1000
// To: Double.NEGATIVE_INFINITY
check("-1000", Double.NEGATIVE_INFINITY);
// From: -0.00
// To: Double.NaN
check("-0.00", Double.NaN);
}
/**
* Test for division by zero (Double)
*/
@Test
public void test_Parse_in_DecimalFormat_Double_DivisionByZero() {
df = new DecimalFormat();
df.setParseBigDecimal(false);
df.setMultiplier(0);
// From: 1000.000
// To: Double.POSITIVE_INFINITY
check("1000.000", Double.POSITIVE_INFINITY);
// From: -1000.000
// To: Double.NEGATIVE_INFINITY
check("-1000.000", Double.NEGATIVE_INFINITY);
// From: 0.0
// To: Double.NaN
check("0.0", Double.NaN);
// From: -0.0 (Double)
// To: Double.NaN
check("-0.0", Double.NaN);
// From: Double.NaN
// To: Double.NaN
check("NaN", Double.NaN);
// From: Double.POSITIVE_INFINITY
// To: Double.NaN
check("\u221e", Double.POSITIVE_INFINITY);
// From: Double.NEGATIVE_INFINITY
// To: Double.NaN
check("-\u221e", Double.NEGATIVE_INFINITY);
}
/**
* Test for division by zero (Long)
*/
@Test
public void test_Parse_in_DecimalFormat_Long_DivisionByZero() {
df = new DecimalFormat();
df.setParseBigDecimal(false);
df.setMultiplier(0);
// From: 1000
// To: Double.POSITIVE_INFINITY
check("1000", Double.POSITIVE_INFINITY);
// From: -1000
// To: Double.NEGATIVE_INFINITY
check("-1000", Double.NEGATIVE_INFINITY);
// From: -000 (Long)
// To: Double.NaN
check("-000", Double.NaN);
}
/**
* Test for normal big numbers which don't have the fraction part
*/
@Test
public void test_Parse_in_DecimalFormat_BigInteger() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
// From: 123...890
// To: BigDecimal 123...890
check(nonsep_int + nonsep_int, new BigDecimal(nonsep_int + nonsep_int));
// From: 123,4...7,890
// To: BigDecimal 1234...7890
check(sep_int + "," + sep_int, new BigDecimal(nonsep_int + nonsep_int));
// From: -000...000123...890
// To: BigDecimal -123...890
check("-" + nonsep_zero + nonsep_int, new BigDecimal("-" + nonsep_int));
// From: -000,0...0,000,123,4...7,890
// To: BigDecimal -123...890
check("-" + sep_zero + "," + sep_int, new BigDecimal("-" + nonsep_int));
}
/**
* Test for normal big numbers which don't have the fraction part with
* multiplier
*/
@Test
public void test_Parse_in_DecimalFormat_BigInteger_usingMultiplier() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
// From: 250,0...0,000
// To: 1000...0000
df.setMultiplier(250000000);
check("250,000,000," + sep_zero, new BigDecimal("1" + nonsep_zero));
// From: -250,0...0,000
// To: -1000...0000
check("-250,000,000," + sep_zero, new BigDecimal("-1" + nonsep_zero));
// From: 250,0...0,000
// To: -1000...0000
df.setMultiplier(-250000000);
check("250,000,000," + sep_zero, new BigDecimal("-1" + nonsep_zero));
// From: -250,0...0,000
// To: 1000...0000
check("-250,000,000," + sep_zero, new BigDecimal("1" + nonsep_zero));
// From: 250,0...0,000E-360
// To: -1000...0000.000...000
check("250,000,000," + sep_zero + "," + sep_zero + "E-360",
new BigDecimal("-1" + nonsep_zero + "." + nonsep_zero));
// Confirm that a division which results in a irrational number is done
// properly
// From: 1000
// To: 333
df.setMultiplier(3);
check("1000", new BigDecimal("333"));
}
/**
* Test for special numbers
* Double.NaN
* Double.POSITIVE_INFINITY
* Double.NEGATIVE_INFINITY
*/
@Test
public void test_Parse_in_DecimalFormat_SpecialNumber() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
String[] numbers = {
"0", "0.0", "25", "25.0", "25.5", "\u221e", "NaN",
"-0", "-0.0", "-25", "-25.0", "-25.5", "-\u221e",
};
int multipliers[] = {5, -5};
Number[][] expected = {
{
new BigDecimal("0"), new BigDecimal("0.0"), new BigDecimal("5"),
new BigDecimal("5.0"), new BigDecimal("5.1"),
Double.POSITIVE_INFINITY, Double.NaN,
new BigDecimal("0"), new BigDecimal("0.0"),
new BigDecimal("-5"), new BigDecimal("-5.0"),
new BigDecimal("-5.1"),
Double.NEGATIVE_INFINITY, Double.NaN,
},
{
new BigDecimal("0"), new BigDecimal("0.0"),
new BigDecimal("-5"), new BigDecimal("-5.0"),
new BigDecimal("-5.1"),
Double.NEGATIVE_INFINITY, Double.NaN,
new BigDecimal("0"), new BigDecimal("0.0"), new BigDecimal("5"),
new BigDecimal("5.0"), new BigDecimal("5.1"),
Double.POSITIVE_INFINITY,
},
};
for (int i = 0; i < multipliers.length; i++) {
df.setMultiplier(multipliers[i]);
for (int j = 0; j < numbers.length; j++) {
check(String.valueOf(numbers[j]), expected[i][j]);
}
}
}
/**
* Test for special numbers
*/
@Test
public void test_Parse_in_DecimalFormat_Other() {
df = new DecimalFormat();
df.setParseBigDecimal(true);
String[] numbers = {
"-9223372036854775808", // Long.MIN_VALUE
};
int multipliers[] = {1, -1};
String[][] expected = {
{"-9223372036854775808"}, // Long.MIN_VALUE
{"9223372036854775808"}, // Long.MAX_VALUE+1 = abs(MIN_VALUE)
};
for (int i = 0; i < multipliers.length; i++) {
df.setMultiplier(multipliers[i]);
for (int j = 0; j < numbers.length; j++) {
check(String.valueOf(numbers[j]),
new BigDecimal(expected[i][j]));
}
}
}
static final String[] patterns = {
" {0, number} ",
" {0, number} ",
" {0, number, currency} ",
" {0, number, currency} ",
" {0, number, percent} ",
" {0, number, percent} ",
" {0, number,#,##0.###E0} ",
" {0, number,#,##0.###E0} ",
" {0, number} ",
" {0, number} ",
" {0, number, integer} ",
" {0, number, integer} ",
" {0, number, currency} ",
" {0, number, currency} ",
" {0, number, percent} ",
" {0, number, percent} ",
" {0, number,#,##0.###E0} ",
" {0, number,#,##0.###E0} ",
};
static final String[] from = {
" 12,345,678,901,234,567,890.98765432109876543210987654321 ",
" -12,345,678,901,234,567,890.98765432109876543210987654321 ",
" $12,345,678,901,234,567,890.98765432109876543210987654321 ",
" ($12,345,678,901,234,567,890.98765432109876543210987654321) ",
" 1,234,567,890,123,456,789,098.76543210987654321098765432100% ",
" -1,234,567,890,123,456,789,098.76543210987654321098765432100% ",
" 12,345,678,901,234,567,890.98765432109876543210987654321E-20 ",
" -12,345,678,901,234,567,890.98765432109876543210987654321E-20 ",
" 9,876,543,210,987,654,321,098,765,432,109,876,543,210 ",
" -9,876,543,210,987,654,321,098,765,432,109,876,543,210 ",
" 9,876,543,210,987,654,321,098,765,432,109,876,543,210E5 ",
" -9,876,543,210,987,654,321,098,765,432,109,876,543,210E-5 ",
" $9,876,543,210,987,654,321,098,765,432,109,876,543,210.00 ",
" ($9,876,543,210,987,654,321,098,765,432,109,876,543,210.00) ",
" 987,654,321,098,765,432,109,876,543,210,987,654,321,012% ",
" -987,654,321,098,765,432,109,876,543,210,987,654,321,012% ",
" 98,765,432,109,876,543,210.98765432109876543210E20 ",
" -987,654,321,098,765,432,109,876,543,210,987,654,321,000,000,000,000,000,000,000E-20 ",
};
static final String[] expected1 = { // isParseIntegerOnly() == false
"12345678901234567890.98765432109876543210987654321",
"-12345678901234567890.98765432109876543210987654321",
"12345678901234567890.98765432109876543210987654321",
"-12345678901234567890.98765432109876543210987654321",
"12345678901234567890.98765432109876543210987654321",
"-12345678901234567890.98765432109876543210987654321",
"0.1234567890123456789098765432109876543210987654321",
"-0.1234567890123456789098765432109876543210987654321",
"9876543210987654321098765432109876543210",
"-9876543210987654321098765432109876543210",
"9.876543210987654321098765432109876543210E44",
"-98765432109876543210987654321098765.43210",
"9876543210987654321098765432109876543210.00",
"-9876543210987654321098765432109876543210.00",
"9876543210987654321098765432109876543210.12",
"-9876543210987654321098765432109876543210.12",
"9876543210987654321098765432109876543210",
"-9876543210987654321098765432109876543210.00000000000000000000",
};
static final int[] parsePosition1 = {
60, 61, 61, 63, 64, 65, 64, 65,
57, 58, 59, 61, 61, 63, 60, 61, 54, 88,
};
/**
* Test for MessageFormat: setParseIntegerOnly(false)
*/
@Test
public void test_Parse_in_MessageFormat_NotParseIntegerOnly() {
for (int i=0; i < patterns.length; i++) {
pp = new ParsePosition(0);
Object[] parsed = null;
try {
MessageFormat mf = new MessageFormat(patterns[i]);
Format[] formats = mf.getFormats();
for (int j=0; j < formats.length; j++) {
((DecimalFormat)formats[j]).setParseBigDecimal(true);
}
parsed = mf.parse(from[i], pp);
if (pp.getErrorIndex() != -1) {
fail("Case" + (i+1) +
": getErrorIndex() returns wrong value. expected:-1, got:"+
pp.getErrorIndex() + " for " + from[i]);
}
if (pp.getIndex() != parsePosition1[i]) {
fail("Case" + (i+1) +
": getIndex() returns wrong value. expected:" +
parsePosition1[i] + ", got:"+ pp.getIndex() +
" for " + from[i]);
}
}
catch(Exception e) {
fail("Unexpected exception: " + e.getMessage());
}
checkType(from[i], getType(new BigDecimal(expected1[i])),
getType((Number)parsed[0]));
checkParse(from[i], new BigDecimal(expected1[i]),
(Number)parsed[0]);
}
}
static final String[] expected2 = { // isParseIntegerOnly() == true
"12345678901234567890",
"-12345678901234567890",
"12345678901234567890",
"-12345678901234567890",
"12345678901234567890",
"-12345678901234567890",
"0",
"0",
"9876543210987654321098765432109876543210",
"-9876543210987654321098765432109876543210",
"9.876543210987654321098765432109876543210E44",
"-98765432109876543210987654321098765.43210",
"9876543210987654321098765432109876543210",
"-9876543210987654321098765432109876543210",
"9876543210987654321098765432109876543210.12",
"-9876543210987654321098765432109876543210.12",
"9876543210987654321098765432109876543210",
"-9876543210987654321098765432109876543210.00000000000000000000",
};
static final int[][] parsePosition2 = { // {errorIndex, index}
/*
* Should keep in mind that the expected result is different from
* DecimalFormat.parse() for some cases. This is because parsing integer
* only will return a successful parse for the subformat, but since the index
* returned is not equal to the length, at the MessageFormat level, this
* will be interpreted as a failed parse, and so the DecimalFormat index
* should be reflected as the MessageFormat errorIndex.
*/
{28, 0}, // parsing stopped at '.'
{29, 0}, // parsing stopped at '.'
{29, 0}, // parsing stopped at '.'
{30, 0}, // parsing stopped at '.'
{31, 0}, // parsing stopped at '.'
{32, 0}, // parsing stopped at '.'
{28, 0}, // parsing stopped at '.'
{29, 0}, // parsing stopped at '.'
{-1, 57}, {-1, 58}, {-1, 59}, {-1, 61},
{56, 0}, // parsing stopped at '.'
{57, 0}, // parsing stopped at '.'
{-1, 60}, {-1, 61},
{28, 0}, // parsing stopped at '.'
{-1, 88},
};
/**
* Test for MessageFormat: setParseIntegerOnly(true)
*/
@Test
public void test_Parse_in_MessageFormat_ParseIntegerOnly() {
for (int i=0; i < patterns.length; i++) {
pp = new ParsePosition(0);
Object[] parsed = null;
try {
MessageFormat mf = new MessageFormat(patterns[i]);
Format[] formats = mf.getFormats();
for (int j=0; j < formats.length; j++) {
((DecimalFormat)formats[j]).setParseBigDecimal(true);
((DecimalFormat)formats[j]).setParseIntegerOnly(true);
}
parsed = mf.parse(from[i], pp);
if (pp.getErrorIndex() != parsePosition2[i][0]) {
fail("Case" + (i+1) +
": getErrorIndex() returns wrong value. expected:" +
parsePosition2[i][0] + ", got:"+ pp.getErrorIndex() +
" for " + from[i]);
}
if (pp.getIndex() != parsePosition2[i][1]) {
fail("Case" + (i+1) +
": getIndex() returns wrong value. expected:" +
parsePosition2[i][1] + ", got:"+ pp.getIndex() +
" for " + from[i]);
}
}
catch(Exception e) {
fail("Unexpected exception: " + e.getMessage());
}
if (parsePosition2[i][0] == -1) {
checkType(from[i], getType(new BigDecimal(expected2[i])),
getType((Number)parsed[0]));
checkParse(from[i], new BigDecimal(expected2[i]),
(Number)parsed[0]);
}
}
}
static final String[] from3 = {
"12,345,678,901,234,567,890.98765432109876543210987654321",
"-12,345,678,901,234,567,890.98765432109876543210987654321",
"9,876,543,210,987,654,321,098,765,432,109,876,543,210",
"-9,876,543,210,987,654,321,098,765,432,109,876,543,210",
"1234556790000E-8",
};
static final String[] expected3 = {
"12345678901234567890",
"-12345678901234567890",
"9876543210987654321098765432109876543210",
"-9876543210987654321098765432109876543210",
"12345.56790000",
};
static final int[][] parsePosition3 = { // {errorIndex, index}
{-1, 26},
{-1, 27},
{-1, 53},
{-1, 54},
{-1, 16},
};
/**
* Test for DecimalFormat: setParseIntegerOnly(true)
*/
@Test
public void test_Parse_in_DecimalFormat_ParseIntegerOnly() {
DecimalFormat df = (DecimalFormat)NumberFormat.getIntegerInstance();
df.setParseBigDecimal(true);
for (int i=0; i < from3.length; i++) {
pp = new ParsePosition(0);
Number parsed = null;
try {
parsed = df.parse(from3[i], pp);
if (pp.getErrorIndex() != parsePosition3[i][0]) {
fail("Case" + (i+1) +
": getErrorIndex() returns wrong value. expected:" +
parsePosition3[i][0] + ", got:"+ pp.getErrorIndex() +
" for " + from3[i]);
}
if (pp.getIndex() != parsePosition3[i][1]) {
fail("Case" + (i+1) +
": getIndex() returns wrong value. expected:" +
parsePosition3[i][1] + ", got:"+ pp.getIndex() +
" for " + from3[i]);
}
}
catch(Exception e) {
fail("Unexpected exception: " + e.getMessage());
}
if (parsePosition3[i][0] == -1) {
checkType(from3[i], getType(new BigDecimal(expected3[i])),
getType(parsed));
checkParse(from3[i], new BigDecimal(expected3[i]), parsed);
}
}
}
protected void check(String from, Number to) {
pp = new ParsePosition(0);
try {
parsed = df.parse(from, pp);
}
catch(Exception e) {
exceptionOccurred = true;
fail(e.getMessage());
}
if (!exceptionOccurred) {
checkParse(from, to, parsed);
checkType(from, getType(to), getType(parsed));
checkParsePosition(from, from.length(), pp.getIndex());
}
}
private void checkParse(String orig, Number expected, Number got) {
if (!expected.equals(got)) {
fail("Parsing... failed." +
"\n original: " + orig +
"\n parsed: " + got +
"\n expected: " + expected + "\n");
}
}
private void checkType(String orig, String expected, String got) {
if (!expected.equals(got)) {
fail("Parsing... unexpected Class returned." +
"\n original: " + orig +
"\n got: " + got +
"\n expected: " + expected + "\n");
}
}
private void checkParsePosition(String orig, int expected, int got) {
if (expected != got) {
fail("Parsing... wrong ParsePosition returned." +
"\n original: " + orig +
"\n got: " + got +
"\n expected: " + expected + "\n");
}
}
private String getType(Number number) {
return number.getClass().getName();
}
}

View file

@ -0,0 +1,186 @@
/*
* Copyright (c) 2003, 2023, 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
* @summary Confirm that the decimal separator is shown when explicitly requested
* (or not shown if not requested). Tests against double, long, BigDecimal,
* and BigInteger with a combination of different patterns.
* @bug 4208135
* @run junit Bug4208135
*/
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.Locale;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
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;
public class Bug4208135 {
private static DecimalFormat df;
// Save JVM default Locale
private static final Locale savedLocale = Locale.getDefault();
// Set JVM default locale to US
@BeforeAll
static void init() {
Locale.setDefault(Locale.US);
}
// Restore JVM default locale
@AfterAll
static void tearDown() {
Locale.setDefault(savedLocale);
}
// Confirm that decimal separator shown when formatting a number
@ParameterizedTest
@MethodSource("fractionalDigitsWithSeparatorProvider")
public void fractionalDigitsWithSeparatorTest(Number num, String expected) {
df = getDF("0.#E0", true);
String actualFormatted = df.format(num);
assertEquals(expected, actualFormatted, getErrMsg("0.#E0", true));
}
// Combination of numbers and a fractional exponent pattern with a separator
private static Stream<Arguments> fractionalDigitsWithSeparatorProvider() {
return Stream.of(
Arguments.of(0.0, "0.E0"),
Arguments.of(10.0, "1.E1"),
Arguments.of(1000.0, "1.E3"),
Arguments.of(0L, "0.E0"),
Arguments.of(10L, "1.E1"),
Arguments.of(1000L, "1.E3"),
Arguments.of(new BigDecimal("0.0"), "0.E0"),
Arguments.of(new BigDecimal("10.0"), "1.E1"),
Arguments.of(new BigDecimal("1000.0"), "1.E3"),
Arguments.of(new BigInteger("00"), "0.E0"),
Arguments.of(new BigInteger("10"), "1.E1"),
Arguments.of(new BigInteger("1000"), "1.E3")
);
}
// Confirm that decimal separator not shown when formatting a number
@ParameterizedTest
@MethodSource("fractionalDigitsNoSeparatorProvider")
public void fractionalDigitsNoSeparatorTest(Number num, String expected) {
df = getDF("0.#E0", false);
String actualFormatted = df.format(num);
assertEquals(expected, actualFormatted, getErrMsg("0.#E0", false));
}
// Combination of numbers and a fractional exponent pattern with no separator
private static Stream<Arguments> fractionalDigitsNoSeparatorProvider() {
return Stream.of(
Arguments.of(0.0, "0E0"),
Arguments.of(10.0, "1E1"),
Arguments.of(1000.0, "1E3"),
Arguments.of(0L, "0E0"),
Arguments.of(10L, "1E1"),
Arguments.of(1000L, "1E3"),
Arguments.of(new BigDecimal("0.0"), "0E0"),
Arguments.of(new BigDecimal("10.0"), "1E1"),
Arguments.of(new BigDecimal("1000.0"), "1E3"),
Arguments.of(new BigInteger("00"), "0E0"),
Arguments.of(new BigInteger("10"), "1E1"),
Arguments.of(new BigInteger("1000"), "1E3")
);
}
// Confirm that decimal separator shown when formatting a number
@ParameterizedTest
@MethodSource("noFractionalDigitsWithSeparatorProvider")
public void noFractionalDigitsWithSeparatorTest(Number num, String expected) {
df = getDF("0.###", true);
String actualFormatted = df.format(num);
assertEquals(expected, actualFormatted, getErrMsg("0.###", true));
}
// Combination of numbers and a non-fractional exponent pattern with a separator
private static Stream<Arguments> noFractionalDigitsWithSeparatorProvider() {
return Stream.of(
Arguments.of(0.0, "0."),
Arguments.of(10.0, "10."),
Arguments.of(1000.0, "1000."),
Arguments.of(0L, "0."),
Arguments.of(10L, "10."),
Arguments.of(1000L, "1000."),
Arguments.of(new BigDecimal("0.0"), "0."),
Arguments.of(new BigDecimal("10.0"), "10."),
Arguments.of(new BigDecimal("1000.0"), "1000."),
Arguments.of(new BigInteger("00"), "0."),
Arguments.of(new BigInteger("10"), "10."),
Arguments.of(new BigInteger("1000"), "1000.")
);
}
// Confirm that decimal separator not shown when formatting a number
@ParameterizedTest
@MethodSource("noFractionalDigitsNoSeparatorProvider")
public void noFractionalDigitsNoSeparatorTest(Number num, String expected) {
df = getDF("0.###", false);
String actualFormatted = df.format(num);
assertEquals(expected, actualFormatted, getErrMsg("0.###", false));
}
// Combination of numbers and a non-fractional exponent pattern with no separator
private static Stream<Arguments> noFractionalDigitsNoSeparatorProvider() {
return Stream.of(
Arguments.of(0.0, "0"),
Arguments.of(10.0, "10"),
Arguments.of(1000.0, "1000"),
Arguments.of(0L, "0"),
Arguments.of(10L, "10"),
Arguments.of(1000L, "1000"),
Arguments.of(new BigDecimal("0.0"), "0"),
Arguments.of(new BigDecimal("10.0"), "10"),
Arguments.of(new BigDecimal("1000.0"), "1000"),
Arguments.of(new BigInteger("00"), "0"),
Arguments.of(new BigInteger("10"), "10"),
Arguments.of(new BigInteger("1000"), "1000")
);
}
// Creates clean DF and sets the pattern and separatorShown value
private static DecimalFormat getDF(String pattern, boolean separatorShown) {
df = new DecimalFormat();
df.applyPattern(pattern);
df.setDecimalSeparatorAlwaysShown(separatorShown);
return df;
}
// Utility to get a helpful error message when values are not as expected
private static String getErrMsg(String pattern, boolean separatorShown) {
return String.format("Fails with pattern= %s, with separatorShown = %s",
pattern, separatorShown);
}
}

View file

@ -0,0 +1,464 @@
/*
* Copyright (c) 2003, 2016, 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
* @summary Confirm that the negative multiplier works as expected.
* @bug 4833877
*/
import java.math.*;
import java.text.*;
import java.util.*;
public class Bug4833877 {
static DecimalFormat df;
static boolean err = false;
public static void main(String[] args) throws Exception {
Locale defaultLoc = Locale.getDefault();
Locale.setDefault(Locale.US);
/* ================================================================ */
df = new DecimalFormat();
df.setMaximumFractionDigits(50);
df.setMultiplier(4);
/*
* Test for double/Double
*/
checkFormat(252.5252525252525, "1,010.10101010101");
checkParse("-1,010.10101010101", -252.5252525252525);
checkFormat(-2222.2222, "-8,888.8888");
checkParse("8888.8888", 2222.2222);
/*
* Test for long/Long
*/
checkFormat(1000L, "4,000");
checkParse("-4,000", -1000L);
checkFormat(-250L, "-1,000");
checkParse("1000", 250L);
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(true);
/*
* Test for BigDecimal
*/
checkFormat(new BigDecimal("22222.222222222222222222222"),
"88,888.888888888888888888888");
checkParse("-88,888.888888888888888888888",
new BigDecimal("-22222.222222222222222222222"));
checkFormat(new BigDecimal("-1111111111111111111.111111111111111111"),
"-4,444,444,444,444,444,444.444444444444444444");
checkParse("4444444444444444444.444444444444444444",
new BigDecimal("1111111111111111111.111111111111111111"));
/*
* Test for BigInteger
*/
checkFormat(new BigInteger("22222222222222222222222222"),
"88,888,888,888,888,888,888,888,888");
checkParse("-88,888,888,888,888,888,888,888,888",
new BigDecimal("-22222222222222222222222222"));
checkFormat(new BigInteger("-1111111111111111111111111"),
"-4,444,444,444,444,444,444,444,444");
checkParse("4444444444444444444444444",
new BigDecimal("1111111111111111111111111"));
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(false);
df.setMultiplier(-4);
/*
* Test for double/Double
*/
checkFormat(252.5252525252525, "-1,010.10101010101");
checkParse("-1,010.10101010101", 252.5252525252525);
checkFormat(-2222.2222, "8,888.8888");
checkParse("8888.8888", -2222.2222);
/*
* Test for long/Long
*/
checkFormat(1000L, "-4,000");
checkParse("-4,000", 1000L);
checkFormat(-250L, "1,000");
checkParse("1000", -250L);
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(true);
/*
* Test for BigDecimal
*/
checkFormat(new BigDecimal("22222.222222222222222222222"),
"-88,888.888888888888888888888");
checkParse("-88,888.888888888888888888888",
new BigDecimal("22222.222222222222222222222"));
checkFormat(new BigDecimal("-1111111111111111111.111111111111111111"),
"4,444,444,444,444,444,444.444444444444444444");
checkParse("4444444444444444444.444444444444444444",
new BigDecimal("-1111111111111111111.111111111111111111"));
/*
* Test for BigInteger
*/
checkFormat(new BigInteger("22222222222222222222222222"),
"-88,888,888,888,888,888,888,888,888");
checkParse("-88,888,888,888,888,888,888,888,888",
new BigDecimal("22222222222222222222222222"));
checkFormat(new BigInteger("-1111111111111111111111111"),
"4,444,444,444,444,444,444,444,444");
checkParse("4444444444444444444444444",
new BigDecimal("-1111111111111111111111111"));
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(false);
df.setMultiplier(-3);
/*
* Test for double/Double
*/
checkFormat(3333.3333333, "-9,999.9999999");
checkParse("-10,000.00000000000", 3333.3333333333335);// rounding error
df.setParseIntegerOnly(true);
checkFormat(-3333.3333333, "9,999.9999999");
checkParse("10,000.00000000000", -3333L);
df.setParseIntegerOnly(false);
checkFormat(-3333.3333333, "9,999.9999999");
checkParse("10,000.00000000000", -3333.3333333333335);// rounding error
/*
* Test for long/Long
*/
checkFormat(3333L, "-9,999");
df.setParseIntegerOnly(true);
checkParse("-10,000", 3333L);
df.setParseIntegerOnly(false);
checkParse("-10000", 3333.3333333333335);// rounding error
checkFormat(-3333L, "9,999");
df.setParseIntegerOnly(true);
checkParse("10,000", -3333L);
df.setParseIntegerOnly(false);
checkParse("10000", -3333.3333333333335);// rounding error
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(true);
/*
* Test for BigDecimal
*/
checkFormat(new BigDecimal("33333.333333333333333333333"),
"-99,999.999999999999999999999");
checkParse("-100,000.000000000000000000000",
new BigDecimal("33333.333333333333333333333"));
checkFormat(new BigDecimal("-33333.333333333333333333333"),
"99,999.999999999999999999999");
checkParse("100,000.000000000000000000000",
new BigDecimal("-33333.333333333333333333333"));
/*
* Test for BigInteger
*/
checkFormat(new BigInteger("33333333333333333333333333"),
"-99,999,999,999,999,999,999,999,999");
checkParse("-100,000,000,000,000,000,000,000,000",
new BigDecimal("33333333333333333333333333"));
checkFormat(new BigInteger("-33333333333333333333333333"),
"99,999,999,999,999,999,999,999,999");
df.setParseIntegerOnly(true);
checkParse("100,000,000,000,000,000,000,000,000.000",
new BigDecimal("-33333333333333333333333333"));
df.setParseIntegerOnly(false);
checkParse("100,000,000,000,000,000,000,000,000.000",
new BigDecimal("-33333333333333333333333333.333"));
/* ================================================================ */
df = new DecimalFormat("0.#E0;-0.#E0");
df.setMaximumFractionDigits(50);
df.setMultiplier(4);
/*
* Test for double/Double
*/
checkFormat(252.5252525252525, "1.01010101010101E3");
checkParse("-1.01010101010101E3", -2.525252525252525E2);
checkFormat(-2222.2222, "-8.8888888E3");
checkParse("8888.8888", 2.2222222E3);
/*
* Test for long/Long
*/
checkFormat(1000L, "4E3");
checkParse("-4E3", -1000L);
checkFormat(-250L, "-1E3");
checkParse("1000", 250L);
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(true);
/*
* Test for BigDecimal
*/
checkFormat(new BigDecimal("22222.222222222222222222222"),
"8.8888888888888888888888888E4");
checkParse("-8.8888888888888888888888888E4",
new BigDecimal("-2.2222222222222222222222222E4"));
checkFormat(new BigDecimal("-1111111111111111111.111111111111111111"),
"-4.444444444444444444444444444444444444E18");
checkParse("4444444444444444444.444444444444444444",
new BigDecimal("1111111111111111111.111111111111111111"));
/*
* Test for BigInteger
*/
checkFormat(new BigInteger("22222222222222222222222222"),
"8.8888888888888888888888888E25");
checkParse("-8.8888888888888888888888888E25",
new BigDecimal("-22222222222222222222222222"));
checkFormat(new BigInteger("-1111111111111111111111111"),
"-4.444444444444444444444444E24");
checkParse("4444444444444444444444444",
new BigDecimal("1111111111111111111111111"));
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(false);
df.setMultiplier(-4);
/*
* Test for double/Double
*/
checkFormat(252.5252525252525, "-1.01010101010101E3");
checkParse("-1.01010101010101E3", 2.525252525252525E2);
checkFormat(-2222.2222, "8.8888888E3");
checkParse("8888.8888", -2.2222222E3);
/*
* Test for long/Long
*/
checkFormat(1000L, "-4E3");
checkParse("-4E3", 1000L);
checkFormat(-250L, "1E3");
checkParse("1000", -250L);
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(true);
/*
* Test for BigDecimal
*/
checkFormat(new BigDecimal("22222.222222222222222222222"),
"-8.8888888888888888888888888E4");
checkParse("-8.8888888888888888888888888E4",
new BigDecimal("2.2222222222222222222222222E4"));
checkFormat(new BigDecimal("-1111111111111111111.111111111111111111"),
"4.444444444444444444444444444444444444E18");
checkParse("4444444444444444444.444444444444444444",
new BigDecimal("-1111111111111111111.111111111111111111"));
/*
* Test for BigInteger
*/
checkFormat(new BigInteger("22222222222222222222222222"),
"-8.8888888888888888888888888E25");
checkParse("-8.8888888888888888888888888E25",
new BigDecimal("22222222222222222222222222"));
checkFormat(new BigInteger("-1111111111111111111111111"),
"4.444444444444444444444444E24");
checkParse("4444444444444444444444444",
new BigDecimal("-1111111111111111111111111"));
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(false);
df.setMultiplier(-3);
/*
* Test for double/Double
*/
checkFormat(3333.3333333, "-9.9999999999E3");
checkParse("-1.00000000000000E3", 3.33333333333333333E2);
df.setParseIntegerOnly(true);
checkFormat(-3333.3333333, "9.9999999999E3");
checkParse("10.00000000000000E3",-3L);
df.setParseIntegerOnly(false);
checkFormat(-3333.3333333, "9.9999999999E3");
checkParse("10.00000000000000E3", -3.33333333333333333E3);
/*
* Test for long/Long
*/
checkFormat(3333L, "-9.999E3");
df.setParseIntegerOnly(true);
checkParse("-1.0E4", 0L);
df.setParseIntegerOnly(false);
checkParse("-1.0E4", 3333.3333333333335);
checkFormat(-3333L, "9.999E3");
df.setParseIntegerOnly(true);
checkParse("10.0E4", -3L);
df.setParseIntegerOnly(false);
checkParse("10.0E4", -33333.3333333333336);
/* ---------------------------------------------------------------- */
df.setParseBigDecimal(true);
/*
* Test for BigDecimal
*/
checkFormat(new BigDecimal("333.333333333333333333333333"),
"-9.99999999999999999999999999E2");
checkParse("-1.0000000000000000000000000E3",
new BigDecimal("3.333333333333333333333333E2"));
df.setParseIntegerOnly(true);
checkFormat(new BigDecimal("-333.333333333333333333333333"),
"9.99999999999999999999999999E2");
checkParse("10.0000000000000000000000000E3",
new BigDecimal("-3"));
df.setParseIntegerOnly(false);
checkFormat(new BigDecimal("-333.333333333333333333333333"),
"9.99999999999999999999999999E2");
checkParse("1.0000000000000000000000000E3",
new BigDecimal("-3.333333333333333333333333E2"));
/*
* Test for BigInteger
*/
checkFormat(new BigInteger("33333333333333333333333333"),
"-9.9999999999999999999999999E25");
checkParse("-100000000000000000000000000",
new BigDecimal("33333333333333333333333333"));
checkFormat(new BigInteger("-33333333333333333333333333"),
"9.9999999999999999999999999E25");
df.setParseIntegerOnly(true);
checkParse("100000000000000000000000000000",
new BigDecimal("-33333333333333333333333333333"));
df.setParseIntegerOnly(false);
checkParse("100000000000000000000000000.000",
new BigDecimal("-33333333333333333333333333.333"));
/* ================================================================ */
Locale.setDefault(defaultLoc);
if (err) {
throw new RuntimeException("Wrong format/parse with DecimalFormat");
}
}
static void checkFormat(Number num, String expected) {
String got = df.format(num);
if (!got.equals(expected)) {
err = true;
System.err.println(" DecimalFormat format(" +
num.getClass().getName() +
") error:" +
"\n\tnumber: " + num +
"\n\tpattern: " + df.toPattern() +
"\n\tmultiplier: " + df.getMultiplier() +
"\n\tgot: " + got +
"\n\texpected: " + expected);
}
}
static void checkParse(String text, Double expected) {
Double got = (Double)df.parse(text, new ParsePosition(0));
if (!got.equals(expected)) {
err = true;
System.err.println(" DecimalFormat parse(double) error:" +
"\n\ttext: " + text +
"\n\tpattern: " + df.toPattern() +
"\n\tmultiplier: " + df.getMultiplier() +
"\n\tgot: " + got +
"\n\texpected: " + expected);
}
}
static void checkParse(String text, Long expected) {
Long got = (Long)df.parse(text, new ParsePosition(0));
if (!got.equals(expected)) {
err = true;
System.err.println(" DecimalFormat parse(long) error:" +
"\n\ttext: " + text +
"\n\tpattern: " + df.toPattern() +
"\n\tmultiplier: " + df.getMultiplier() +
"\n\tgot: " + got +
"\n\texpected: " + expected);
}
}
static void checkParse(String text, BigDecimal expected) {
BigDecimal got = (BigDecimal)df.parse(text, new ParsePosition(0));
if (!got.equals(expected)) {
err = true;
System.err.println(" DecimalFormat parse(BigDecimal) error:" +
"\n\ttext: " + text +
"\n\tpattern: " + df.toPattern() +
"\n\tmultiplier: " + df.getMultiplier() +
"\n\tgot: " + got +
"\n\texpected: " + expected);
}
}
}

View file

@ -0,0 +1,282 @@
/*
* Copyright (c) 2003, 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 4838107 8008577 8174269
* @summary Confirm that DecimalFormat can format a number with a negative
* exponent number correctly. Tests also involve using a DecimalFormat
* with a custom pattern or a custom minus sign.
* @run junit Bug4838107
*/
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
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;
import static org.junit.jupiter.api.Assertions.fail;
/*
* This bug is about exponential formatting. But I added test cases for:
* - Double and BigDecimal numbers which don't have exponent parts.
* - Long and BigInteger numbers which don't support exponential
* notation.
* because there are few test cases for suffix and prefix.
* And also, I added test cases to guarantee further formatting and
* parsing using the same DecimalFormat instance will not change the
* Number's value anymore.
*/
public class Bug4838107 {
// Save JVM default Locale
private static final Locale savedLocale = Locale.getDefault();
// Set JVM default Locale to US
@BeforeAll
static void init() {
Locale.setDefault(Locale.US);
}
// Restore the original JVM default locale
@AfterAll
static void tearDown() {
Locale.setDefault(savedLocale);
}
// Check that negative exponent number recognized for doubles
@ParameterizedTest
@MethodSource("doubles")
public void doubleTest(Number num, String str, DecimalFormat df) {
test(num, str, df);
}
// Provides a double to be formatted, which is compared to the expected String.
// Additionally, provides a DecimalFormat to do the formatting (can have a custom
// pattern and minus sign). Given in the form (double, String, DecimalFormat).
private static Stream<Arguments> doubles() {
DecimalFormat defaultDf = new DecimalFormat();
DecimalFormat customDf1 = getDecimalFormat("<P>#.###E00<S>", 'm');
DecimalFormat customDf2 = getDecimalFormat("<P>#.###E00<S>;#.###E00", 'm');
DecimalFormat customDf3 = getDecimalFormat("#.###E00;<P>#.###E00<S>", 'm');
DecimalFormat customDf4 = getDecimalFormat("<P>#.###E00<S>;<p>-#.###E00<s>", 'm');
return Stream.of(
// Test with default pattern
Arguments.of(1234D, "1,234", defaultDf),
Arguments.of(0.1234, "0.123", defaultDf), // rounded
Arguments.of(-1234D, "-1,234", defaultDf),
Arguments.of(-0.1234, "-0.123", defaultDf), // rounded
Arguments.of(Double.POSITIVE_INFINITY, "\u221e", defaultDf),
Arguments.of(Double.NEGATIVE_INFINITY, "-\u221e", defaultDf),
Arguments.of(Double.NaN, "NaN", defaultDf), // without prefix and suffix
Arguments.of(0.0, "0", defaultDf),
Arguments.of(-0.0, "-0", defaultDf), // with the minus sign
// Test with a pattern and the minus sign
Arguments.of(1234D, "<P>1.234E03<S>", customDf1),
Arguments.of(0.1234, "<P>1.234Em01<S>", customDf1),
Arguments.of(-1234D, "m<P>1.234E03<S>", customDf1),
Arguments.of(-0.1234, "m<P>1.234Em01<S>", customDf1),
Arguments.of(1234D, "<P>1.234E03<S>", customDf2),
Arguments.of(0.1234, "<P>1.234Em01<S>", customDf2),
Arguments.of(-1234D, "1.234E03", customDf2),
Arguments.of(-0.1234, "1.234Em01", customDf2),
Arguments.of(1234D, "1.234E03", customDf3),
Arguments.of(0.1234, "1.234Em01", customDf3),
Arguments.of(-1234D, "<P>1.234E03<S>", customDf3),
Arguments.of(-0.1234, "<P>1.234Em01<S>", customDf3),
Arguments.of(1234D, "<P>1.234E03<S>", customDf4),
Arguments.of(0.1234, "<P>1.234Em01<S>", customDf4),
Arguments.of(-1234D, "<p>m1.234E03<s>", customDf4),
Arguments.of(-0.1234, "<p>m1.234Em01<s>", customDf4),
Arguments.of(Double.POSITIVE_INFINITY, "<P>\u221e<S>", customDf4),
Arguments.of(Double.NEGATIVE_INFINITY, "<p>m\u221e<s>", customDf4),
Arguments.of(Double.NaN, "NaN", customDf4), // without prefix and suffix
Arguments.of(0.0, "<P>0E00<S>", customDf4),
Arguments.of(-0.0, "<p>m0E00<s>", customDf4) // with the minus sign
);
}
// Check that negative exponent number recognized for longs
@ParameterizedTest
@MethodSource("longs")
public void longTest(Number num, String str, DecimalFormat df) {
test(num, str, df);
}
// Same as doubles() data provider, but with long values
// Given in the form (long, String, DecimalFormat).
private static Stream<Arguments> longs() {
DecimalFormat defaultDf = new DecimalFormat();
DecimalFormat customDf = getDecimalFormat(
"<P>#,###<S>;<p>-#,###<s>", 'm');
return Stream.of(
// Test with default pattern
Arguments.of(123456789L, "123,456,789", defaultDf),
Arguments.of(-123456789L, "-123,456,789", defaultDf),
Arguments.of(0L, "0", defaultDf),
Arguments.of(-0L, "0", defaultDf),
// Test with a pattern and the minus sign
Arguments.of(123456789L, "<P>123,456,789<S>", customDf),
Arguments.of(-123456789L, "<p>m123,456,789<s>", customDf),
Arguments.of(0L, "<P>0<S>", customDf),
Arguments.of(-0L, "<P>0<S>", customDf)
);
}
// Check that negative exponent number recognized for bigDecimals
@ParameterizedTest
@MethodSource("bigDecimals")
public void bigDecimalTest(Number num, String str, DecimalFormat df) {
test(num, str, df);
}
// Same as doubles() data provider, but with BigDecimal values
// Given in the form (BigDecimal, String, DecimalFormat).
private static Stream<Arguments> bigDecimals() {
DecimalFormat defaultDf = new DecimalFormat();
DecimalFormat customDf = getDecimalFormat(
"<P>#.####################E00<S>;<p>-#.####################E00<s>", 'm');
return Stream.of(
// Test with default pattern
Arguments.of(new BigDecimal("123456789012345678901234567890"),
"123,456,789,012,345,678,901,234,567,890", defaultDf),
Arguments.of(new BigDecimal("0.000000000123456789012345678901234567890"),
"0", defaultDf),
Arguments.of(new BigDecimal("-123456789012345678901234567890"),
"-123,456,789,012,345,678,901,234,567,890", defaultDf),
Arguments.of(new BigDecimal("-0.000000000123456789012345678901234567890"),
"-0", defaultDf),
Arguments.of(new BigDecimal("0"), "0", defaultDf),
Arguments.of(new BigDecimal("-0"), "0", defaultDf),
// Test with a pattern and the minus sign
Arguments.of(new BigDecimal("123456789012345678901234567890"),
"<P>1.23456789012345678901E29<S>", customDf),
Arguments.of(new BigDecimal("0.000000000123456789012345678901234567890"),
"<P>1.23456789012345678901Em10<S>", customDf),
Arguments.of(new BigDecimal("-123456789012345678901234567890"),
"<p>m1.23456789012345678901E29<s>", customDf),
Arguments.of(new BigDecimal("-0.000000000123456789012345678901234567890"),
"<p>m1.23456789012345678901Em10<s>", customDf),
Arguments.of(new BigDecimal("0"), "<P>0E00<S>", customDf),
Arguments.of(new BigDecimal("-0"), "<P>0E00<S>", customDf)
);
}
// Check that negative exponent number recognized for bigIntegers
@ParameterizedTest
@MethodSource("bigIntegers")
public void bigIntegerTest(Number num, String str, DecimalFormat df) {
test(num, str, df);
}
// Same as doubles() data provider, but with BigInteger values
// Given in the form (BigInteger, String, DecimalFormat).
private static Stream<Arguments> bigIntegers() {
DecimalFormat defaultDf = new DecimalFormat();
DecimalFormat customDf = getDecimalFormat(
"<P>#,###<S>;<p>-#,###<s>", 'm');
return Stream.of(
// Test with default pattern
Arguments.of(new BigInteger("123456789012345678901234567890"),
"123,456,789,012,345,678,901,234,567,890", defaultDf),
Arguments.of(new BigInteger("-123456789012345678901234567890"),
"-123,456,789,012,345,678,901,234,567,890", defaultDf),
Arguments.of(new BigInteger("0"), "0", defaultDf),
Arguments.of(new BigInteger("-0"), "0", defaultDf),
// Test with a pattern and the minus sign
Arguments.of(new BigInteger("123456789012345678901234567890"),
"<P>123,456,789,012,345,678,901,234,567,890<S>", customDf),
Arguments.of(new BigInteger("-123456789012345678901234567890"),
"<p>m123,456,789,012,345,678,901,234,567,890<s>", customDf),
Arguments.of(new BigInteger("0"), "<P>0<S>", customDf),
Arguments.of(new BigInteger("-0"), "<P>0<S>", customDf)
);
}
// Check that the formatted value is correct and also check that
// it can be round-tripped via parse() and format()
private static void test(Number num, String str, DecimalFormat df) {
String formatted = df.format(num);
assertEquals(str, formatted, String.format("DecimalFormat format(%s) " +
"Error: number: %s, minus sign: %s", num.getClass().getName(), num, df.getDecimalFormatSymbols().getMinusSign()));
if (num instanceof BigDecimal || num instanceof BigInteger) {
df.setParseBigDecimal(true);
}
testRoundTrip(formatted, str, num, df);
}
// Test that a parsed value can be round-tripped via format() and parse()
private static void testRoundTrip(String formatted, String str,
Number num, DecimalFormat df) {
Number parsed1 = null, parsed2 = null;
try {
parsed1 = df.parse(formatted);
formatted = df.format(parsed1);
parsed2 = df.parse(formatted);
assertEquals(parsed2, parsed1, """
DecimalFormat round trip parse(%s) error:
original number: %s
parsed number: %s
(%s)
formatted number: %s
re-parsed number: %s
(%s)
minus sign: %s
""".formatted(num.getClass().getName(), str, parsed1, parsed1.getClass().getName(),
formatted, parsed2, parsed2.getClass().getName(), df.getDecimalFormatSymbols().getMinusSign()));
}
catch (Exception e) {
fail("""
DecimalFormat parse(%s) threw an Exception: %s
original number: %s
parsed number: %s
(%s)
formatted number: %s
re-parsed number: %s
(%s)
minus sign: %s
""".formatted(num.getClass().getName(), e.getMessage(), str, parsed1, parsed1.getClass().getName(),
formatted, parsed2, parsed2.getClass().getName(), df.getDecimalFormatSymbols().getMinusSign()));
}
}
// Set up custom DecimalFormat with DecimalFormatSymbols
private static DecimalFormat getDecimalFormat(String pattern, char minusSign) {
DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
df.applyPattern(pattern);
dfs.setMinusSign(minusSign);
df.setDecimalFormatSymbols(dfs);
return df;
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2003, 2025, 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 4944439 8372609
* @summary Confirm that numbers where all digits after the decimal separator are 0
* and which are between Long.MIN_VALUE and Long.MAX_VALUE are returned
* as Long(not double).
* @run junit Bug4944439
*/
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Locale;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
public class Bug4944439 {
private static final NumberFormat df = NumberFormat.getInstance(Locale.US);
// Check return type and value returned by DecimalFormat.parse() for longs
@ParameterizedTest
@MethodSource("longs")
public void parseLongTest(String s) {
// This was originally intended to ensure a ParseException is not thrown
Number parsedNumber = assertDoesNotThrow(() -> df.parse(s),
"DecimalFormat.parse(\"%s\") should not throw an Exception");
assertInstanceOf(Long.class, parsedNumber,
"DecimalFormat.parse(\"%s\") did not return Long");
// Grab integer portion of value
Long expectedVal = Long.valueOf(s.substring(0, s.indexOf('.')));
assertEquals(parsedNumber, expectedVal,
"DecimalFormat.parse(\"%s\") returned numerically incorrect value");
}
// Test some values between Long.MIN_VALUE and Long.MAX_VALUE
private static Stream<String> longs() {
ArrayList<String> longs = new ArrayList<>();
addLongData(Long.MIN_VALUE, Long.MIN_VALUE+10, longs);
addLongData(-10, 10, longs);
addLongData(Long.MAX_VALUE-10, Long.MAX_VALUE-1, longs);
longs.add("9223372036854775807.00");
longs.add("0.0");
return longs.stream();
}
// Utility to add values between parameters(long, to) to testLongs ArrayList
private static void addLongData(long from, long to, ArrayList<String> testLongs){
for (long l = from; l <= to; l++) {
testLongs.add(l + ".00");
}
}
// Check return type and value returned by DecimalFormat.parse() for doubles
@ParameterizedTest
@MethodSource("doubles")
public void parseDoubleTest(String s) {
// This was originally intended to ensure a ParseException is not thrown
Number parsedNumber = assertDoesNotThrow(() -> df.parse(s),
"DecimalFormat.parse(\"%s\") should not throw an Exception");
assertInstanceOf(Double.class, parsedNumber,
"DecimalFormat.parse(\"%s\") did not return Double");
Double expectedVal = Double.valueOf(s);
assertEquals(parsedNumber, expectedVal,
"DecimalFormat.parse(\"%s\") returned numerically incorrect value");
}
// Check values not between Long.MIN_VALUE and Long.MAX_VALUE
private static Stream<String> doubles() {
return Stream.of(
"-9223372036854775809", // Long.MIN_VALUE-1
"9223372036854775808", // Long.MAX_VALUE+1
"-0.0"
);
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2004, 2023, 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 4990596
* @summary Make sure that any subclass of Number can be formatted using
* DecimalFormat.format() without throwing an exception.
* @run junit Bug4990596
*/
import java.text.DecimalFormat;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
public class Bug4990596 {
// Test that a custom subclass of Number can be formatted by
// DecimalFormat without throwing an IllegalArgumentException
@Test
public void formatSubclassedNumberTest() {
assertDoesNotThrow(() -> new DecimalFormat().format(new MutableInteger(0)),
"DecimalFormat.format() should support subclasses of Number");
}
// A custom subclass of Number. Prior to this fix, if an instance of this
// class was formatted by DecimalFormat, an exception would be thrown.
@SuppressWarnings("serial")
public static class MutableInteger extends Number {
public int value;
public MutableInteger() {
}
public MutableInteger(int value) {
this.value = value;
}
public double doubleValue() {
return this.value;
}
public float floatValue() {
return this.value;
}
public int intValue() {
return this.value;
}
public long longValue() {
return this.value;
}
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2005, 2023, 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
* @summary Confirm that AtomicInteger and AtomicLong are formatted correctly.
* That is, make sure they are not treated as a double when formatted
* anymore (which can result in the loss of precision).
* @bug 6278616
* @run junit Bug6278616
*/
import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
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;
public class Bug6278616 {
private static final NumberFormat nf = NumberFormat.getInstance();
// Test that NumberFormat formats numerically equivalent int
// and AtomicInteger values the same
@ParameterizedTest
@MethodSource("ints")
public void formattedAtomicIntTest(int testInt) {
String formattedInt = nf.format(testInt);
String formattedAtomicInt = nf.format(new AtomicInteger(testInt));
assertEquals(formattedAtomicInt, formattedInt, "Formatting numerically" +
" equivalent AtomicInteger and int should produce the same String value");
}
// Various int values
private static int[] ints() {
return new int[] { Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE};
}
// Test that NumberFormat formats numerically equivalent long
// and AtomicLong values the same
@ParameterizedTest
@MethodSource("longs")
public void formattedAtomicLongTest(long testLong) {
String formattedLong = nf.format(testLong);
String formattedAtomicLong = nf.format(new AtomicLong(testLong));
assertEquals(formattedAtomicLong, formattedLong, "Formatting numerically" +
" equivalent AtomicLong and long should produce the same String value");
}
// Various long values
private static long[] longs() {
return new long[] { Long.MIN_VALUE, -1, 0, 1, Long.MAX_VALUE};
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2015, 2025, 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 8132125 8202537 8354548
* @summary Checks Swiss' number elements
* @modules jdk.localedata
* @run junit Bug8132125
*/
import java.text.NumberFormat;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Bug8132125 {
// Ensure the CLDRConverter does not omit the Swiss number elements
@Test
public void swissNumElementsTest() {
Locale deCH = Locale.of("de", "CH");
NumberFormat nf = NumberFormat.getInstance(deCH);
// "\u002E" as decimal separator, "\u0027" as grouping separator
String expected = "54'839'483.142";
String actual = nf.format(54839483.1415);
assertEquals(expected, actual, "incorrect number elements for de_CH");
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2001, 2025, 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 4290801 4942982 5102005 8008577 8021121 8210153 8227313 8301991
* 8174269
* @summary Basic tests for currency formatting.
* Tests both COMPAT and CLDR data.
* @modules jdk.localedata
* @run junit CurrencyFormat
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Currency;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
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;
public class CurrencyFormat {
// Tests the formatting of data for COMPAT + CLDR under various currencies
// Using a NumberFormat generated by getCurrencyInstance()
@ParameterizedTest
@MethodSource("currencyFormatDataProvider")
public void currencyFormatTest(String expected, Currency currency,
NumberFormat format, Locale locale) {
if (currency != null) {
format.setCurrency(currency);
int digits = currency.getDefaultFractionDigits();
format.setMinimumFractionDigits(digits);
format.setMaximumFractionDigits(digits);
}
String result = format.format(1234.56);
assertEquals(expected, result, String.format("Failed with locale: %s%s",
locale, (currency == null ? ", default currency" : (", currency: " + currency))));
}
// Generate a combination of expected data for 1234.56 formatted
// under various currencies/locale provider/locale
private static Stream<Arguments> currencyFormatDataProvider() {
ArrayList<Arguments> data = new ArrayList<Arguments>();
Locale[] locales = {
Locale.US,
Locale.JAPAN,
Locale.GERMANY,
Locale.ITALY,
Locale.of("it", "IT", "EURO"),
Locale.forLanguageTag("de-AT"),
Locale.forLanguageTag("fr-CH"),
};
Currency[] currencies = {
null,
Currency.getInstance("USD"),
Currency.getInstance("JPY"),
Currency.getInstance("DEM"),
Currency.getInstance("EUR"),
};
String[][] expectedCLDRData = {
{"$1,234.56", "$1,234.56", "\u00a51,235", "DEM1,234.56", "\u20ac1,234.56"},
{"\uFFE51,235", "$1,234.56", "\uFFE51,235", "DEM1,234.56", "\u20ac1,234.56"},
{"1.234,56\u00a0\u20ac", "1.234,56\u00a0$", "1.235\u00a0\u00a5", "1.234,56\u00a0DM", "1.234,56\u00a0\u20ac"},
{"1.234,56\u00a0\u20ac", "1.234,56\u00a0USD", "1.235\u00a0JPY", "1.234,56\u00a0DEM", "1.234,56\u00a0\u20ac"},
{"1.234,56\u00a0\u20ac", "1.234,56\u00a0USD", "1.235\u00a0JPY", "1.234,56\u00a0DEM", "1.234,56\u00a0\u20ac"},
{"\u20ac\u00a01.234,56", "$\u00a01.234,56", "\u00a5\u00a01.235", "DM\u00a01.234,56", "\u20ac\u00a01.234,56"},
{"1\u202f234.56\u00a0CHF", "1\u202f234.56\u00a0$US", "1\u202f235\u00a0JPY", "1\u202f234.56\u00a0DEM", "1\u202f234.56\u00a0\u20ac"},
};
for (int i = 0; i < locales.length; i++) {
Locale locale = locales[i];
NumberFormat format = NumberFormat.getCurrencyInstance(locale);
for (int j = 0; j < currencies.length; j++) {
Currency currency = currencies[j];
String expected = expectedCLDRData[i][j];
data.add(Arguments.of(expected, currency, format, locale));
}
}
return data.stream();
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2005, 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 4068067
* @summary test NumberFormat with exponential separator symbols. It also tests the new
* public methods in DecimalFormatSymbols, setExponentSeparator() and
* getExponentSeparator()
* @run junit DFSExponential
*/
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
public class DFSExponential {
@Test
public void TestDFSExponential() {
DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US);
String[] pat = { "0.####E0", "00.000E00", "##0.####E000", "0.###E0;[0.###E0]"};
double[] val = { 0.01234, 123456789, 1.23e300, -3.141592653e-271};
String[][] valFormat = {
{"1.234x10^-2", "1.2346x10^8", "1.23x10^300", "-3.1416x10^-271"},
{"12.340x10^-03", "12.346x10^07", "12.300x10^299", "-31.416x10^-272"},
{"12.34x10^-003", "123.4568x10^006", "1.23x10^300", "-314.1593x10^-273"},
{"1.234x10^-2", "1.235x10^8", "1.23x10^300", "[3.142x10^-271]"},
};
System.out.println("Default exponent separator: "+sym.getExponentSeparator());
try {
sym.setExponentSeparator("x10^");
} catch (NullPointerException e){
fail("null String was passed to set an exponent separator symbol");
}
System.out.println("Current exponent separator: "+sym.getExponentSeparator());
for (int p=0; p<pat.length; ++p){
DecimalFormat fmt = new DecimalFormat(pat[p], sym);
System.out.println(" Pattern: " + fmt.toPattern());
String locPattern = fmt.toLocalizedPattern();
System.out.println(" Localized pattern: "+locPattern);
for (int v=0; v<val.length; ++v) {
String s = fmt.format(val[v]);
System.out.println(" " + val[v]+" --> "+s);
if (valFormat[p][v].equals(s)){
System.out.println(": Passed");
} else{
fail(" Failed: Should be formatted as "+valFormat[p][v]+ "but got "+s);
}
}
}
}
}

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2019, 2025, 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 8220309 8230284
* @library /java/text/testlib
* @summary Test String representation of MinusSign/Percent/PerMill symbols.
* This test assumes CLDR has numbering systems for "arab" and
* "arabext", and their minus/percent representations include
* BiDi formatting control characters.
* @run junit/othervm DFSMinusPerCentMill
*/
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import static org.junit.jupiter.api.Assertions.assertEquals;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class DFSMinusPerCentMill {
private enum Type {
NUMBER, PERCENT, CURRENCY, INTEGER, COMPACT, PERMILL
}
private static final Locale US_ARAB = Locale.forLanguageTag("en-US-u-nu-arab");
private static final Locale US_ARABEXT = Locale.forLanguageTag("en-US-u-nu-arabext");
private static final double SRC_NUM = -1234.56;
Object[][] formatData() {
return new Object[][] {
// Locale, FormatStyle, expected format, expected single char symbol
{US_ARAB, Type.NUMBER, "\u061c-\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666"},
{US_ARAB, Type.PERCENT, "\u061c-\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066a\u061c"},
{US_ARAB, Type.CURRENCY, "\u061c-\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666\u00a0$"},
{US_ARAB, Type.INTEGER, "\u061c-\u0661\u066c\u0662\u0663\u0665"},
{US_ARAB, Type.COMPACT, "\u061c-\u0661K"},
{US_ARAB, Type.PERMILL, "\u061c-\u0661\u0662\u0663\u0664\u0665\u0666\u0660\u0609"},
{US_ARABEXT, Type.NUMBER, "\u200e-\u200e\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"},
{US_ARABEXT, Type.PERCENT, "\u200e-\u200e\u06f1\u06f2\u06f3\u066c\u06f4\u06f5\u06f6\u066a"},
{US_ARABEXT, Type.CURRENCY, "\u200e-\u200e$\u00a0\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"},
{US_ARABEXT, Type.INTEGER, "\u200e-\u200e\u06f1\u066c\u06f2\u06f3\u06f5"},
{US_ARABEXT, Type.COMPACT, "\u200e-\u200e\u06f1K"},
{US_ARABEXT, Type.PERMILL, "\u200e-\u200e\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f0\u0609"},
};
}
Object[][] charSymbols() {
return new Object[][]{
// Locale, percent, per mille, minus sign
{US_ARAB, '\u066a', '\u0609', '-'},
{US_ARABEXT, '\u066a', '\u0609', '-'},
};
}
@ParameterizedTest
@MethodSource("formatData")
void testFormatData(Locale l, Type style, String expected) {
NumberFormat nf = null;
switch (style) {
case NUMBER:
nf = NumberFormat.getNumberInstance(l);
break;
case PERCENT:
nf = NumberFormat.getPercentInstance(l);
break;
case CURRENCY:
nf = NumberFormat.getCurrencyInstance(l);
break;
case INTEGER:
nf = NumberFormat.getIntegerInstance(l);
break;
case COMPACT:
nf = NumberFormat.getCompactNumberInstance(l, NumberFormat.Style.SHORT);
break;
case PERMILL:
nf = new DecimalFormat("#.#\u2030", DecimalFormatSymbols.getInstance(l));
break;
}
assertEquals(expected, nf.format(SRC_NUM));
}
@ParameterizedTest
@MethodSource("charSymbols")
void testCharSymbols(Locale l, char percent, char permill, char minus) {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(l);
assertEquals(percent, dfs.getPercent());
assertEquals(permill, dfs.getPerMill());
assertEquals(minus, dfs.getMinusSign());
}
@Test
void testSerialization() throws Exception {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(dfs);
DecimalFormatSymbols dfsSerialized = (DecimalFormatSymbols)new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray())
).readObject();
assertEquals(dfsSerialized, dfs);
// set minus/percent/permille
dfs.setMinusSign('a');
dfs.setPercent('b');
dfs.setPerMill('c');
bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(dfs);
dfsSerialized = (DecimalFormatSymbols)new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray())
).readObject();
assertEquals(dfsSerialized, dfs);
}
}

View file

@ -0,0 +1,256 @@
/*
* Copyright (c) 1998, 2023, 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
* @summary test International Decimal Format API
* @run junit IntlTestDecimalFormatAPI
*/
/*
(C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
(C) Copyright IBM Corp. 1996, 1997 - All Rights Reserved
The original version of this source code and documentation is copyrighted and
owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These materials are
provided under terms of a License Agreement between Taligent and Sun. This
technology is protected by multiple US and International patents. This notice and
attribution to Taligent may not be removed.
Taligent is a registered trademark of Taligent, Inc.
*/
import java.text.*;
import java.util.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
public class IntlTestDecimalFormatAPI
{
// This test checks various generic API methods in DecimalFormat to achieve 100% API coverage.
@Test
public void TestAPI()
{
Locale reservedLocale = Locale.getDefault();
try {
System.out.println("DecimalFormat API test---"); System.out.println("");
Locale.setDefault(Locale.ENGLISH);
// ======= Test constructors
System.out.println("Testing DecimalFormat constructors");
DecimalFormat def = new DecimalFormat();
final String pattern = new String("#,##0.# FF");
DecimalFormat pat = null;
try {
pat = new DecimalFormat(pattern);
}
catch (IllegalArgumentException e) {
fail("ERROR: Could not create DecimalFormat (pattern)");
}
DecimalFormatSymbols symbols =
new DecimalFormatSymbols(Locale.FRENCH);
DecimalFormat cust1 = new DecimalFormat(pattern, symbols);
// ======= Test clone(), assignment, and equality
System.out.println("Testing clone() and equality operators");
Format clone = (Format) def.clone();
if( ! def.equals(clone)) {
fail("ERROR: Clone() failed");
}
// ======= Test various format() methods
System.out.println("Testing various format() methods");
// final double d = -10456.0037; // this appears as
// -10456.003700000001 on NT
// final double d = -1.04560037e-4; // this appears as
// -1.0456003700000002E-4 on NT
final double d = -10456.00370000000000; // this works!
final long l = 100000000;
System.out.println("" + d + " is the double value");
StringBuffer res1 = new StringBuffer();
StringBuffer res2 = new StringBuffer();
StringBuffer res3 = new StringBuffer();
StringBuffer res4 = new StringBuffer();
FieldPosition pos1 = new FieldPosition(0);
FieldPosition pos2 = new FieldPosition(0);
FieldPosition pos3 = new FieldPosition(0);
FieldPosition pos4 = new FieldPosition(0);
res1 = def.format(d, res1, pos1);
System.out.println("" + d + " formatted to " + res1);
res2 = pat.format(l, res2, pos2);
System.out.println("" + l + " formatted to " + res2);
res3 = cust1.format(d, res3, pos3);
System.out.println("" + d + " formatted to " + res3);
res4 = cust1.format(l, res4, pos4);
System.out.println("" + l + " formatted to " + res4);
// ======= Test parse()
System.out.println("Testing parse()");
String text = new String("-10,456.0037");
ParsePosition pos = new ParsePosition(0);
String patt = new String("#,##0.#");
pat.applyPattern(patt);
double d2 = pat.parse(text, pos).doubleValue();
if(d2 != d) {
fail("ERROR: Roundtrip failed (via parse(" +
d2 + " != " + d + ")) for " + text);
}
System.out.println(text + " parsed into " + (long) d2);
// ======= Test getters and setters
System.out.println("Testing getters and setters");
final DecimalFormatSymbols syms = pat.getDecimalFormatSymbols();
def.setDecimalFormatSymbols(syms);
if(!pat.getDecimalFormatSymbols().equals(
def.getDecimalFormatSymbols())) {
fail("ERROR: set DecimalFormatSymbols() failed");
}
String posPrefix;
pat.setPositivePrefix("+");
posPrefix = pat.getPositivePrefix();
System.out.println("Positive prefix (should be +): " + posPrefix);
if(posPrefix != "+") {
fail("ERROR: setPositivePrefix() failed");
}
String negPrefix;
pat.setNegativePrefix("-");
negPrefix = pat.getNegativePrefix();
System.out.println("Negative prefix (should be -): " + negPrefix);
if(negPrefix != "-") {
fail("ERROR: setNegativePrefix() failed");
}
String posSuffix;
pat.setPositiveSuffix("_");
posSuffix = pat.getPositiveSuffix();
System.out.println("Positive suffix (should be _): " + posSuffix);
if(posSuffix != "_") {
fail("ERROR: setPositiveSuffix() failed");
}
String negSuffix;
pat.setNegativeSuffix("~");
negSuffix = pat.getNegativeSuffix();
System.out.println("Negative suffix (should be ~): " + negSuffix);
if(negSuffix != "~") {
fail("ERROR: setNegativeSuffix() failed");
}
long multiplier = 0;
pat.setMultiplier(8);
multiplier = pat.getMultiplier();
System.out.println("Multiplier (should be 8): " + multiplier);
if(multiplier != 8) {
fail("ERROR: setMultiplier() failed");
}
int groupingSize = 0;
pat.setGroupingSize(2);
groupingSize = pat.getGroupingSize();
System.out.println("Grouping size (should be 2): " + (long) groupingSize);
if(groupingSize != 2) {
fail("ERROR: setGroupingSize() failed");
}
pat.setDecimalSeparatorAlwaysShown(true);
boolean tf = pat.isDecimalSeparatorAlwaysShown();
System.out.println("DecimalSeparatorIsAlwaysShown (should be true) is " +
(tf ? "true" : "false"));
if(tf != true) {
fail("ERROR: setDecimalSeparatorAlwaysShown() failed");
}
String funkyPat;
funkyPat = pat.toPattern();
System.out.println("Pattern is " + funkyPat);
String locPat;
locPat = pat.toLocalizedPattern();
System.out.println("Localized pattern is " + locPat);
// ======= Test applyPattern()
System.out.println("Testing applyPattern()");
String p1 = new String("#,##0.0#;(#,##0.0#)");
System.out.println("Applying pattern " + p1);
pat.applyPattern(p1);
String s2;
s2 = pat.toPattern();
System.out.println("Extracted pattern is " + s2);
if( ! s2.equals(p1) ) {
fail("ERROR: toPattern() result did not match " +
"pattern applied");
}
String p2 = new String("#,##0.0# FF;(#,##0.0# FF)");
System.out.println("Applying pattern " + p2);
pat.applyLocalizedPattern(p2);
String s3;
s3 = pat.toLocalizedPattern();
System.out.println("Extracted pattern is " + s3);
if( ! s3.equals(p2) ) {
fail("ERROR: toLocalizedPattern() result did not match " +
"pattern applied");
}
// ======= Test getStaticClassID()
// logln("Testing instanceof()");
// try {
// NumberFormat test = new DecimalFormat();
// if (! (test instanceof DecimalFormat)) {
// errln("ERROR: instanceof failed");
// }
// }
// catch (Exception e) {
// errln("ERROR: Couldn't create a DecimalFormat");
// }
} finally {
// restore the reserved locale
Locale.setDefault(reservedLocale);
}
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 1998, 2025, 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 8282625 8366517
* @summary test International Decimal Format Symbols
* @run junit IntlTestDecimalFormatSymbols
*/
/*
(C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
(C) Copyright IBM Corp. 1996, 1997 - All Rights Reserved
The original version of this source code and documentation is copyrighted and
owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These materials are
provided under terms of a License Agreement between Taligent and Sun. This
technology is protected by multiple US and International patents. This notice and
attribution to Taligent may not be removed.
Taligent is a registered trademark of Taligent, Inc.
*/
import java.text.*;
import java.util.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
public class IntlTestDecimalFormatSymbols
{
// Test the API of DecimalFormatSymbols; primarily a simple get/set set.
@Test
public void TestSymbols()
{
DecimalFormatSymbols fr = new DecimalFormatSymbols(Locale.FRENCH);
DecimalFormatSymbols en = new DecimalFormatSymbols(Locale.ENGLISH);
if(en.equals(fr)) {
fail("ERROR: English DecimalFormatSymbols equal to French");
}
// just do some VERY basic tests to make sure that get/set work
if (!fr.getLocale().equals(Locale.FRENCH)) {
fail("ERROR: French DecimalFormatSymbols not Locale.FRENCH");
}
if (!en.getLocale().equals(Locale.ENGLISH)) {
fail("ERROR: English DecimalFormatSymbols not Locale.ENGLISH");
}
char zero = en.getZeroDigit();
fr.setZeroDigit(zero);
if(fr.getZeroDigit() != en.getZeroDigit()) {
fail("ERROR: get/set ZeroDigit failed");
}
char group = en.getGroupingSeparator();
fr.setGroupingSeparator(group);
if(fr.getGroupingSeparator() != en.getGroupingSeparator()) {
fail("ERROR: get/set GroupingSeparator failed");
}
char decimal = en.getDecimalSeparator();
fr.setDecimalSeparator(decimal);
if(fr.getDecimalSeparator() != en.getDecimalSeparator()) {
fail("ERROR: get/set DecimalSeparator failed");
}
char perMill = en.getPerMill();
fr.setPerMill(perMill);
if(fr.getPerMill() != en.getPerMill()) {
fail("ERROR: get/set PerMill failed");
}
char percent = en.getPercent();
fr.setPercent(percent);
if(fr.getPercent() != en.getPercent()) {
fail("ERROR: get/set Percent failed");
}
char digit = en.getDigit();
fr.setDigit(digit);
if(fr.getPercent() != en.getPercent()) {
fail("ERROR: get/set Percent failed");
}
char patternSeparator = en.getPatternSeparator();
fr.setPatternSeparator(patternSeparator);
if(fr.getPatternSeparator() != en.getPatternSeparator()) {
fail("ERROR: get/set PatternSeparator failed");
}
String infinity = en.getInfinity();
fr.setInfinity(infinity);
String infinity2 = fr.getInfinity();
if(! infinity.equals(infinity2)) {
fail("ERROR: get/set Infinity failed");
}
String nan = en.getNaN();
fr.setNaN(nan);
String nan2 = fr.getNaN();
if(! nan.equals(nan2)) {
fail("ERROR: get/set NaN failed");
}
char minusSign = en.getMinusSign();
fr.setMinusSign(minusSign);
if(fr.getMinusSign() != en.getMinusSign()) {
fail("ERROR: get/set MinusSign failed");
}
// char exponential = en.getExponentialSymbol();
// fr.setExponentialSymbol(exponential);
// if(fr.getExponentialSymbol() != en.getExponentialSymbol()) {
// errln("ERROR: get/set Exponential failed");
// }
DecimalFormatSymbols foo = new DecimalFormatSymbols();
en = (DecimalFormatSymbols) fr.clone();
if(! en.equals(fr)) {
fail("ERROR: Clone failed");
}
}
@Test
void nullLocaleTest() {
assertThrows(NullPointerException.class, () -> new DecimalFormatSymbols(null));
assertThrows(NullPointerException.class, () -> DecimalFormatSymbols.getInstance(null));
}
}

View file

@ -0,0 +1,222 @@
/*
* Copyright (c) 1998, 2023, 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
* @summary test International Number Format API
* @modules jdk.localedata
* @run junit IntlTestNumberFormatAPI
*/
/*
(C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
(C) Copyright IBM Corp. 1996, 1997 - All Rights Reserved
The original version of this source code and documentation is copyrighted and
owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These materials are
provided under terms of a License Agreement between Taligent and Sun. This
technology is protected by multiple US and International patents. This notice and
attribution to Taligent may not be removed.
Taligent is a registered trademark of Taligent, Inc.
*/
import java.text.*;
import java.util.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
public class IntlTestNumberFormatAPI
{
// This test checks various generic API methods in DecimalFormat to achieve 100% API coverage.
@Test
public void TestAPI()
{
Locale reservedLocale = Locale.getDefault();
try {
System.out.println("NumberFormat API test---"); System.out.println("");
Locale.setDefault(Locale.ENGLISH);
// ======= Test constructors
System.out.println("Testing NumberFormat constructors");
NumberFormat def = NumberFormat.getInstance();
NumberFormat fr = NumberFormat.getInstance(Locale.FRENCH);
NumberFormat cur = NumberFormat.getCurrencyInstance();
NumberFormat cur_fr =
NumberFormat.getCurrencyInstance(Locale.FRENCH);
NumberFormat per = NumberFormat.getPercentInstance();
NumberFormat per_fr =
NumberFormat.getPercentInstance(Locale.FRENCH);
// ======= Test equality
System.out.println("Testing equality operator");
if( per_fr.equals(cur_fr) ) {
fail("ERROR: == failed");
}
// ======= Test various format() methods
System.out.println("Testing various format() methods");
// final double d = -10456.0037; // this appears as
// -10456.003700000001 on NT
// final double d = -1.04560037e-4; // this appears as
// -1.0456003700000002E-4 on NT
final double d = -10456.00370000000000; // this works!
final long l = 100000000;
String res1 = new String();
String res2 = new String();
StringBuffer res3 = new StringBuffer();
StringBuffer res4 = new StringBuffer();
StringBuffer res5 = new StringBuffer();
StringBuffer res6 = new StringBuffer();
FieldPosition pos1 = new FieldPosition(0);
FieldPosition pos2 = new FieldPosition(0);
FieldPosition pos3 = new FieldPosition(0);
FieldPosition pos4 = new FieldPosition(0);
res1 = cur_fr.format(d);
System.out.println( "" + d + " formatted to " + res1);
res2 = cur_fr.format(l);
System.out.println("" + l + " formatted to " + res2);
res3 = cur_fr.format(d, res3, pos1);
System.out.println( "" + d + " formatted to " + res3);
res4 = cur_fr.format(l, res4, pos2);
System.out.println("" + l + " formatted to " + res4);
res5 = cur_fr.format(d, res5, pos3);
System.out.println("" + d + " formatted to " + res5);
res6 = cur_fr.format(l, res6, pos4);
System.out.println("" + l + " formatted to " + res6);
// ======= Test parse()
System.out.println("Testing parse()");
// String text = new String("-10,456.0037");
String text = new String("-10456,0037");
ParsePosition pos = new ParsePosition(0);
ParsePosition pos01 = new ParsePosition(0);
double d1 = ((Number)fr.parseObject(text, pos)).doubleValue();
if(d1 != d) {
fail("ERROR: Roundtrip failed (via parse()) for " + text);
}
System.out.println(text + " parsed into " + d1);
double d2 = fr.parse(text, pos01).doubleValue();
if(d2 != d) {
fail("ERROR: Roundtrip failed (via parse()) for " + text);
}
System.out.println(text + " parsed into " + d2);
double d3 = 0;
try {
d3 = fr.parse(text).doubleValue();
}
catch (ParseException e) {
fail("ERROR: parse() failed");
}
if(d3 != d) {
fail("ERROR: Roundtrip failed (via parse()) for " + text);
}
System.out.println(text + " parsed into " + d3);
// ======= Test getters and setters
System.out.println("Testing getters and setters");
final Locale[] locales = NumberFormat.getAvailableLocales();
long count = locales.length;
System.out.println("Got " + count + " locales" );
for(int i = 0; i < count; i++) {
String name;
name = locales[i].getDisplayName();
System.out.println(name);
}
fr.setParseIntegerOnly( def.isParseIntegerOnly() );
if(fr.isParseIntegerOnly() != def.isParseIntegerOnly() ) {
fail("ERROR: setParseIntegerOnly() failed");
}
fr.setGroupingUsed( def.isGroupingUsed() );
if(fr.isGroupingUsed() != def.isGroupingUsed() ) {
fail("ERROR: setGroupingUsed() failed");
}
fr.setMaximumIntegerDigits( def.getMaximumIntegerDigits() );
if(fr.getMaximumIntegerDigits() != def.getMaximumIntegerDigits() ) {
fail("ERROR: setMaximumIntegerDigits() failed");
}
fr.setMinimumIntegerDigits( def.getMinimumIntegerDigits() );
if(fr.getMinimumIntegerDigits() != def.getMinimumIntegerDigits() ) {
fail("ERROR: setMinimumIntegerDigits() failed");
}
fr.setMaximumFractionDigits( def.getMaximumFractionDigits() );
if(fr.getMaximumFractionDigits() != def.getMaximumFractionDigits() ) {
fail("ERROR: setMaximumFractionDigits() failed");
}
fr.setMinimumFractionDigits( def.getMinimumFractionDigits() );
if(fr.getMinimumFractionDigits() != def.getMinimumFractionDigits() ) {
fail("ERROR: setMinimumFractionDigits() failed");
}
// ======= Test getStaticClassID()
// logln("Testing instanceof()");
// try {
// NumberFormat test = new DecimalFormat();
// if (! (test instanceof DecimalFormat)) {
// errln("ERROR: instanceof failed");
// }
// }
// catch (Exception e) {
// errln("ERROR: Couldn't create a DecimalFormat");
// }
} finally {
// restore the reserved locale
Locale.setDefault(reservedLocale);
}
}
}

View file

@ -0,0 +1,251 @@
/*
* Copyright (c) 2025, 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 8363972
* @summary Unit tests for lenient minus parsing
* @modules jdk.localedata
* java.base/java.text:+open
* @run junit LenientMinusSignTest
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.invoke.MethodHandles;
import java.text.CompactNumberFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.stream.Stream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class LenientMinusSignTest {
private static final Locale FINNISH = Locale.of("fi");
private static final DecimalFormatSymbols DFS =
new DecimalFormatSymbols(Locale.ROOT);
private static final String MINUS_PATTERN = "\u002D";
// "parseLenient" data from CLDR v47. These data are subject to change
private static Stream<String> minus() {
return Stream.of(
MINUS_PATTERN, // "-" Hyphen-Minus
"\uFF0D", // "-" Fullwidth Hyphen-Minus
"\uFE63", // "﹣" Small Hyphen-Minus
"\u2010", // "‐" Hyphen
"\u2011", // "‑" Non-Breaking Hyphen
"\u2012", // "‒" Figure Dash
"\u2013", // "–" En Dash
"\u2212", // "−" Minus Sign
"\u207B", // "⁻" Superscript Minus
"\u208B", // "₋" Subscript Minus
"\u2796" // "➖" Heavy Minus Sign
);
}
@Test
void testFinnishMinus() throws ParseException {
// originally reported in JDK-8189097
// Should not throw a ParseException
assertEquals(NumberFormat.getInstance(FINNISH).parse(MINUS_PATTERN + "1,5"), -1.5);
}
@Test
void testFinnishMinusStrict() {
// Should throw a ParseException
var nf = NumberFormat.getInstance(FINNISH);
nf.setStrict(true);
assertThrows(ParseException.class, () -> nf.parse(MINUS_PATTERN + "1,5"));
}
@Test
void testReadObject() throws IOException, ClassNotFoundException, ParseException {
// check if deserialized NF works with lenient minus. Using the Finnish example
var nf = NumberFormat.getInstance(FINNISH);
NumberFormat nfDeser;
byte[] serialized;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos)) {
out.writeObject(nf);
out.flush();
serialized = bos.toByteArray();
}
try (ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
ObjectInputStream in = new ObjectInputStream(bis)) {
nfDeser = (NumberFormat) in.readObject();
}
assertEquals(nfDeser.parse(MINUS_PATTERN + "1,5"), -1.5);
}
// White box test. modifies the private `lenientMinusSigns` field in the DFS
@Test
void testSupplementary() throws IllegalAccessException, NoSuchFieldException, ParseException {
var dfs = new DecimalFormatSymbols(Locale.ROOT);
MethodHandles.privateLookupIn(DecimalFormatSymbols.class, MethodHandles.lookup())
.findVarHandle(DecimalFormatSymbols.class, "lenientMinusSigns", String.class)
.set(dfs, "-🙂");
// Direct match. Should succeed
var df = new DecimalFormat("#.#;🙂#.#", dfs);
assertEquals(df.parse("🙂1.5"), -1.5);
// Fail if the lengths of negative prefixes differ
assertThrows(ParseException.class, () -> df.parse("-1.5"));
var df2= new DecimalFormat("#.#;-#.#", dfs);
assertThrows(ParseException.class, () -> df2.parse("🙂1.5"));
}
@Nested
class DecimalFormatTest {
private static final String PREFIX = "+#;-#";
private static final String SUFFIX = "#+;#-";
private static final String LONG_PREFIX = "pos#;-neg#";
private static final String LONG_SUFFIX = "#pos;#neg-";
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLenientPrefix(String sign) throws ParseException {
var df = new DecimalFormat(PREFIX, DFS);
df.setStrict(false);
assertEquals(MINUS_PATTERN + "1", df.format(df.parse(sign + "1")));
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLenientSuffix(String sign) throws ParseException {
var df = new DecimalFormat(SUFFIX, DFS);
df.setStrict(false);
assertEquals("1" + MINUS_PATTERN, df.format(df.parse("1" + sign)));
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testStrictPrefix(String sign) throws ParseException {
var df = new DecimalFormat(PREFIX, DFS);
df.setStrict(true);
if (sign.equals(MINUS_PATTERN)) {
assertEquals(MINUS_PATTERN + "1", df.format(df.parse(sign + "1")));
} else {
assertThrows(ParseException.class, () -> df.parse(sign + "1"));
}
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testStrictSuffix(String sign) throws ParseException {
var df = new DecimalFormat(SUFFIX, DFS);
df.setStrict(true);
if (sign.equals(MINUS_PATTERN)) {
assertEquals("1" + MINUS_PATTERN, df.format(df.parse("1" + sign)));
} else {
assertThrows(ParseException.class, () -> df.parse("1" + sign));
}
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLongPrefix(String sign) throws ParseException {
var df = new DecimalFormat(LONG_PREFIX, DFS);
assertEquals(MINUS_PATTERN + "neg1", df.format(df.parse(sign + "neg1")));
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLongSuffix(String sign) throws ParseException {
var df = new DecimalFormat(LONG_SUFFIX, DFS);
assertEquals("1neg" + MINUS_PATTERN, df.format(df.parse("1neg" + sign)));
}
}
@Nested
class CompactNumberFormatTest {
private static final String[] PREFIX = {"+0;-0"};
private static final String[] SUFFIX = {"0+;0-"};
private static final String[] LONG_PREFIX = {"pos0;-neg0"};
private static final String[] LONG_SUFFIX = {"0pos;0neg-"};
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLenientPrefix(String sign) throws ParseException {
var cnf = new CompactNumberFormat("0", DFS, PREFIX);
cnf.setStrict(false);
assertEquals(MINUS_PATTERN + "1", cnf.format(cnf.parse(sign + "1")));
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLenientSuffix(String sign) throws ParseException {
var cnf = new CompactNumberFormat("0", DFS, SUFFIX);
cnf.setStrict(false);
assertEquals("1" + MINUS_PATTERN, cnf.format(cnf.parse("1" + sign)));
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testStrictPrefix(String sign) throws ParseException {
var cnf = new CompactNumberFormat("0", DFS, PREFIX);
cnf.setStrict(true);
if (sign.equals(MINUS_PATTERN)) {
assertEquals(MINUS_PATTERN + "1", cnf.format(cnf.parse(sign + "1")));
} else {
assertThrows(ParseException.class, () -> cnf.parse(sign + "1"));
}
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testStrictSuffix(String sign) throws ParseException {
var cnf = new CompactNumberFormat("0", DFS, SUFFIX);
cnf.setStrict(true);
if (sign.equals(MINUS_PATTERN)) {
assertEquals("1" + MINUS_PATTERN, cnf.format(cnf.parse("1" + sign)));
} else {
assertThrows(ParseException.class, () -> cnf.parse("1" + sign));
}
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLongPrefix(String sign) throws ParseException {
var cnf = new CompactNumberFormat("0", DFS, LONG_PREFIX);
assertEquals(MINUS_PATTERN + "neg1", cnf.format(cnf.parse(sign + "neg1")));
}
@ParameterizedTest
@MethodSource("LenientMinusSignTest#minus")
public void testLongSuffix(String sign) throws ParseException {
var cnf = new CompactNumberFormat("0", DFS, LONG_SUFFIX);
assertEquals("1neg" + MINUS_PATTERN, cnf.format(cnf.parse( "1neg" + sign)));
}
}
}

View file

@ -0,0 +1,480 @@
/*
* 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 8333456 8335668
* @summary Test suite for NumberFormat parsing when lenient.
* @run junit/othervm -Duser.language=en -Duser.country=US LenientParseTest
* @run junit/othervm -Duser.language=ja -Duser.country=JP LenientParseTest
* @run junit/othervm -Duser.language=zh -Duser.country=CN LenientParseTest
* @run junit/othervm -Duser.language=tr -Duser.country=TR LenientParseTest
* @run junit/othervm -Duser.language=de -Duser.country=DE LenientParseTest
* @run junit/othervm -Duser.language=fr -Duser.country=FR LenientParseTest
* @run junit/othervm -Duser.language=ar -Duser.country=AR LenientParseTest
*/
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 lenient 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. When lenient, parsing only fails
// if the prefix and/or suffix are not found, or the first character after the
// prefix is un-parseable. The tested locales all use groupingSize of 3.
public class LenientParseTest {
// Used to retrieve the locale's expected symbols
private static final DecimalFormatSymbols dfs =
new DecimalFormatSymbols(Locale.getDefault());
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);
// All NumberFormats should parse leniently (which is the default)
static {
// To effectively test compactNumberFormat, these should be set accordingly
cmpctFmt.setParseIntegerOnly(false);
cmpctFmt.setGroupingUsed(true);
}
// ---- NumberFormat tests ----
// Test prefix/suffix behavior with a predefined DecimalFormat
// Non-localized, only run once
@ParameterizedTest
@MethodSource("badParseStrings")
@EnabledIfSystemProperty(named = "user.language", matches = "en")
public void numFmtFailParseTest(String toParse, int expectedErrorIndex) {
// Format with grouping size = 3, prefix = a, suffix = b
DecimalFormat nonLocalizedDFmt = new DecimalFormat("a#,#00.00b");
failParse(nonLocalizedDFmt, toParse, expectedErrorIndex);
}
// All input Strings should parse fully and return the expected value.
// Expected index should be the length of the parse string, since it parses fully
@ParameterizedTest
@MethodSource("validFullParseStrings")
public void numFmtSuccessFullParseTest(String toParse, double expectedValue) {
assertEquals(expectedValue, successParse(dFmt, toParse, toParse.length()));
}
// All input Strings should parse partially and return expected value
// with the expected final index
@ParameterizedTest
@MethodSource("validPartialParseStrings")
public void numFmtSuccessPartialParseTest(String toParse, double expectedValue,
int expectedIndex) {
assertEquals(expectedValue, successParse(dFmt, toParse, expectedIndex));
}
// Parse partially due to no grouping
@ParameterizedTest
@MethodSource("noGroupingParseStrings")
public void numFmtStrictGroupingNotUsed(String toParse, double expectedValue, int expectedIndex) {
dFmt.setGroupingUsed(false);
assertEquals(expectedValue, successParse(dFmt, toParse, expectedIndex));
dFmt.setGroupingUsed(true);
}
// Parse partially due to integer only
@ParameterizedTest
@MethodSource("integerOnlyParseStrings")
public void numFmtStrictIntegerOnlyUsed(String toParse, int expectedValue, int expectedIndex) {
dFmt.setParseIntegerOnly(true);
assertEquals(expectedValue, successParse(dFmt, toParse, expectedIndex));
dFmt.setParseIntegerOnly(false);
}
// 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);
}
@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);
// Upon non-numeric in exponent, parse will still successfully complete
// but index should end on the last valid char in exponent
assertEquals(1.23E45, successParse(fmt, "1.23E45.123", 7));
assertEquals(1.23E45, successParse(fmt, "1.23E45.", 7));
assertEquals(1.23E45, successParse(fmt, "1.23E45FOO3222", 7));
}
// ---- CurrencyFormat tests ----
// All input Strings should pass and return expected value.
@ParameterizedTest
@MethodSource("currencyValidFullParseStrings")
public void currFmtSuccessParseTest(String toParse, double expectedValue) {
assertEquals(expectedValue, successParse(cFmt, toParse, toParse.length()));
}
// Strings may parse partially or fail. This is because the mapped
// data may cause the error to occur before the suffix can be found, (if the locale
// uses a suffix).
@ParameterizedTest
@MethodSource("currencyValidPartialParseStrings")
public void currFmtParseTest(String toParse, double expectedValue,
int expectedIndex) {
if (cFmt.getPositiveSuffix().length() > 0) {
// Since the error will occur before suffix is found, exception is thrown.
failParse(cFmt, toParse, expectedIndex);
} else {
// Empty suffix, thus even if the error occurs, we have already found the
// prefix, and simply parse partially
assertEquals(expectedValue, successParse(cFmt, toParse, expectedIndex));
}
}
// ---- PercentFormat tests ----
// All input Strings should pass and return expected value.
@ParameterizedTest
@MethodSource("percentValidFullParseStrings")
public void percentFmtSuccessParseTest(String toParse, double expectedValue) {
assertEquals(expectedValue, successParse(pFmt, toParse, toParse.length()));
}
// Strings may parse partially or fail. This is because the mapped
// data may cause the error to occur before the suffix can be found, (if the locale
// uses a suffix).
@ParameterizedTest
@MethodSource("percentValidPartialParseStrings")
public void percentFmtParseTest(String toParse, double expectedValue,
int expectedIndex) {
if (pFmt.getPositiveSuffix().length() > 0) {
// Since the error will occur before suffix is found, exception is thrown.
failParse(pFmt, toParse, expectedIndex);
} else {
// Empty suffix, thus even if the error occurs, we have already found the
// prefix, and simply parse partially
assertEquals(expectedValue, successParse(pFmt, toParse, expectedIndex));
}
}
// ---- CompactNumberFormat tests ----
// Can match to both the decimalFormat patterns and the compact patterns
// 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("compactValidPartialParseStrings")
@EnabledIfSystemProperty(named = "user.language", matches = "en")
public void compactFmtFailParseTest(String toParse, double expectedValue, int expectedErrorIndex) {
assertEquals(expectedValue, successParse(cmpctFmt, toParse, expectedErrorIndex));
}
@ParameterizedTest
@MethodSource("compactValidFullParseStrings")
@EnabledIfSystemProperty(named = "user.language", matches = "en")
public void compactFmtSuccessParseTest(String toParse, double expectedValue) {
assertEquals(expectedValue, successParse(cmpctFmt, toParse, toParse.length()));
}
// 8333456: Parse values with no compact suffix -> which allows parsing to iterate
// position to the same value as string length which throws
// StringIndexOutOfBoundsException upon charAt invocation
@ParameterizedTest
@MethodSource("compactValidNoSuffixParseStrings")
@EnabledIfSystemProperty(named = "user.language", matches = "en")
public void compactFmtSuccessParseIntOnlyTest(String toParse, double expectedValue) {
cmpctFmt.setParseIntegerOnly(true);
assertEquals(expectedValue, successParse(cmpctFmt, toParse, toParse.length()));
cmpctFmt.setParseIntegerOnly(false);
}
// ---- Helper test methods ----
// Method is used when a String should parse successfully. This does not indicate
// that the entire String was used, however. The index and errorIndex values
// should be as expected.
private double successParse(NumberFormat fmt, String toParse, int expectedIndex) {
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");
return parsedValue.doubleValue();
}
// Method is used when a String should fail parsing. Indicated by either a thrown
// ParseException, or null is returned depending on which parse method is invoked.
// errorIndex should be as expected.
private void failParse(NumberFormat fmt, String toParse, int expectedErrorIndex) {
ParsePosition pp = new ParsePosition(0);
assertThrows(ParseException.class, () -> fmt.parse(toParse));
assertNull(fmt.parse(toParse, pp));
assertEquals(expectedErrorIndex, pp.getErrorIndex());
}
// ---- Data Providers ----
// Strings that should fail when parsed leniently.
// Given as Arguments<String, expectedErrorIndex>
// Non-localized data. For reference, the pattern of nonLocalizedDFmt is
// "a#,#00.00b"
private static Stream<Arguments> badParseStrings() {
return Stream.of(
// No prefix
Arguments.of("1,1b", 0),
// No suffix
Arguments.of("a1,11", 5),
// Digit does not follow the last grouping separator
// Current behavior fails on the grouping separator
Arguments.of("a1,11,z", 5),
// No suffix after grouping
Arguments.of("a1,11,", 5),
// No prefix and suffix
Arguments.of("1,11", 0),
// First character after prefix is un-parseable
// Behavior is to expect error index at 0, not 1
Arguments.of("ac1,11", 0));
}
// 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 parse successfully, and consume the entire String
// Form of Arguments(parseString, expectedParsedNumber)
private static Stream<Arguments> validFullParseStrings() {
return Stream.of(
// Many subsequent grouping symbols
Arguments.of("1,,,1", 11d),
Arguments.of("11,,,11,,,11", 111111d),
// Bad grouping size (with decimal)
Arguments.of("1,1.", 11d),
Arguments.of("11,111,11.", 1111111d),
// Improper grouping size (with decimal and digits after)
Arguments.of("1,1.1", 11.1d),
Arguments.of("1,11.1", 111.1d),
Arguments.of("1,1111.1", 11111.1d),
Arguments.of("11,111,11.1", 1111111.1d),
// Starts with grouping symbol
Arguments.of(",111,,1,1", 11111d),
Arguments.of(",1", 1d),
Arguments.of(",,1", 1d),
// Leading Zeros (not digits)
Arguments.of("000,1,1", 11d),
Arguments.of("000,111,11,,1", 111111d),
Arguments.of("0,000,1,,1,1", 111d),
Arguments.of("1,234.00", 1234d),
Arguments.of("1,234.0", 1234d),
Arguments.of("1,234.", 1234d),
Arguments.of("1,234.00123", 1234.00123d),
Arguments.of("1,234.012", 1234.012d),
Arguments.of("1,234.224", 1234.224d),
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]));
}
// Strings that should parse successfully, but do not use the entire String
// Form of Arguments(parseString, expectedParsedNumber, expectedIndex)
private static Stream<Arguments> validPartialParseStrings() {
return Stream.of(
// End with grouping symbol
Arguments.of("11,", 11d, 2),
Arguments.of("11,,", 11d, 3),
Arguments.of("11,,,", 11d, 4),
// Random chars that aren't the expected symbols
Arguments.of("1,1P111", 11d, 3),
Arguments.of("1.1P111", 1.1d, 3),
Arguments.of("1P,1111", 1d, 1),
Arguments.of("1P.1111", 1d, 1),
Arguments.of("1,1111P", 11111d, 6),
// Grouping occurs after decimal separator)
Arguments.of("1.11,11", 1.11d, 4),
Arguments.of("1.,11,11", 1d, 2))
.map(args -> Arguments.of(
localizeText(String.valueOf(args.get()[0])), args.get()[1], args.get()[2]));
}
// Test data input for when parse integer only is true
// Form of Arguments(parseString, expectedParsedNumber, expectedIndex)
private static Stream<Arguments> integerOnlyParseStrings() {
return Stream.of(
Arguments.of("1234.1234", 1234, 4),
Arguments.of("1234.12", 1234, 4),
Arguments.of("1234.1a", 1234, 4),
Arguments.of("1234.", 1234, 4))
.map(args -> Arguments.of(
localizeText(String.valueOf(args.get()[0])), args.get()[1], args.get()[2]));
}
// Test data input for when no grouping is true
// Form of Arguments(parseString, expectedParsedNumber, expectedIndex)
private static Stream<Arguments> noGroupingParseStrings() {
return Stream.of(
Arguments.of("12,34", 12d, 2),
Arguments.of("1234,", 1234d, 4),
Arguments.of("123,456.789", 123d, 3))
.map(args -> Arguments.of(
localizeText(String.valueOf(args.get()[0])), args.get()[1], args.get()[2]));
}
// Mappers for respective data providers to adjust values accordingly
// Localized percent prefix/suffix is added, with appropriate expected values
// adjusted. Expected parsed number should be divided by 100.
private static Stream<Arguments> percentValidPartialParseStrings() {
return validPartialParseStrings().map(args ->
Arguments.of(pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
(double) args.get()[1] / 100, (int) args.get()[2] + pFmt.getPositivePrefix().length())
);
}
private static Stream<Arguments> percentValidFullParseStrings() {
return validFullParseStrings().map(args -> Arguments.of(
pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
(double) args.get()[1] / 100)
);
}
// Mappers for respective data providers to adjust values accordingly
// Localized percent prefix/suffix is added, with appropriate expected values
// adjusted. Separators replaced for monetary versions.
private static Stream<Arguments> currencyValidPartialParseStrings() {
return validPartialParseStrings().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], (int) args.get()[2] + cFmt.getPositivePrefix().length())
);
}
private static Stream<Arguments> currencyValidFullParseStrings() {
return validFullParseStrings().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> compactValidPartialParseStrings() {
return Stream.concat(validPartialParseStrings().map(args -> Arguments.of(args.get()[0],
args.get()[1], args.get()[2])), validPartialParseStrings().map(args -> Arguments.of(args.get()[0] + "K",
args.get()[1], args.get()[2]))
);
}
private static Stream<Arguments> compactValidFullParseStrings() {
return Stream.concat(validFullParseStrings().map(args -> Arguments.of(args.get()[0],
args.get()[1])), validFullParseStrings().map(args -> Arguments.of(args.get()[0] + "K",
(double)args.get()[1] * 1000.0))
);
}
// No compact suffixes
private static Stream<Arguments> compactValidNoSuffixParseStrings() {
return Stream.of(
Arguments.of("5", 5),
Arguments.of("50", 50),
Arguments.of("50.", 50),
Arguments.of("5,000", 5000),
Arguments.of("5,000.", 5000),
Arguments.of("5,000.00", 5000)
);
}
// 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 == '0') {
sb.append(dfs.getZeroDigit());
} else {
sb.append(c);
}
}
return sb.toString();
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2012, 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 7073852 8008577 8174269 8333582
* @summary Support multiple scripts for digits and decimal symbols per locale
* @run main MultipleNumberScriptTest
*/
import java.text.*;
import java.util.*;
public class MultipleNumberScriptTest {
static Locale[] locales = {
Locale.of("ar"),
Locale.of("ar", "EG"),
Locale.of("ar", "DZ"),
Locale.forLanguageTag("ar-EG-u-nu-arab"),
Locale.forLanguageTag("ar-EG-u-nu-latn"),
Locale.forLanguageTag("ar-DZ-u-nu-arab"),
Locale.forLanguageTag("ar-DZ-u-nu-latn"),
Locale.forLanguageTag("ee"),
Locale.forLanguageTag("ee-GH"),
Locale.forLanguageTag("ee-GH-u-nu-latn"),
Locale.of("th", "TH", "TH"),
Locale.forLanguageTag("th-TH"),
Locale.forLanguageTag("th-TH-u-nu-thai"),
Locale.forLanguageTag("th-TH-u-nu-hoge"),
};
// expected numbering system for each locale
static String[] expectedNumSystem = {
"latn", // ar - CLDR-17553
"arab", // ar-EG
"latn", // ar-DZ
"arab", // ar-EG-u-nu-arab
"latn", // ar-EG-u-nu-latn
"arab", // ar-DZ-u-nu-arab
"latn", // ar-DZ-u-nu-latn
"latn", // ee
"latn", // ee-GH
"latn", // ee-GH-u-nu-latn
"thai", // th_TH_TH
"latn", // th-TH
"thai", // th-TH-u-nu-thai
"latn", // th-TH-u-nu-hoge (invalid)
};
public static void main(String[] args) {
int num = 123456;
for (int i = 0; i < locales.length; i++) {
NumberFormat nf = NumberFormat.getIntegerInstance(locales[i]);
String formatted = nf.format(num);
System.out.printf("%s is %s in %s locale (expected in %s script).\n",
num, formatted, locales[i], expectedNumSystem[i]);
if (!checkResult(formatted, expectedNumSystem[i])) {
throw new RuntimeException("test failed. expected number system was not returned.");
}
}
}
static boolean checkResult(String formatted, String numSystem) {
switch (numSystem) {
case "arab":
return formatted.charAt(0) == '\u0661';
case "latn":
return formatted.charAt(0) == '1';
case "thai":
return formatted.charAt(0) == '\u0e51';
default:
return false;
}
}
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2005, 2016, 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 4092330 6246348
* @summary Tests for rounding mode in NumberFormat
*/
import java.math.*;
import java.text.*;
public class NumberFormatRounding {
static final String AE = "ArithmeticException";
static final double src[] = {5.5, 2.5, 1.6, 1.1, 1.0, -1.0, -1.1, -1.6, -2.5, -5.5,
5.501, -5.501, 5.500, -5.500, 1.001, -1.001, 4.501, -4.501, 4.500, -4.500};
static final String up[] = {"6", "3", "2", "2", "1", "-1", "-2", "-2", "-3", "-6",
"6", "-6", "6", "-6", "2", "-2", "5", "-5", "5", "-5"};
static final String down[] = {"5", "2", "1", "1", "1", "-1", "-1", "-1", "-2", "-5",
"5", "-5", "5", "-5", "1", "-1", "4", "-4", "4", "-4"};
static final String ceiling[] = {"6", "3", "2", "2", "1", "-1", "-1", "-1", "-2", "-5",
"6", "-5", "6", "-5", "2", "-1", "5", "-4", "5", "-4"};
static final String floor[] = {"5", "2", "1", "1", "1", "-1", "-2", "-2", "-3", "-6",
"5", "-6", "5", "-6", "1", "-2", "4", "-5", "4", "-5"};
static final String half_up[] = {"6", "3", "2", "1", "1", "-1", "-1", "-2", "-3", "-6",
"6", "-6", "6", "-6", "1", "-1", "5", "-5", "5", "-5"};
static final String half_down[] = {"5", "2", "2", "1", "1", "-1", "-1", "-2", "-2", "-5",
"6", "-6", "5", "-5", "1", "-1", "5", "-5", "4", "-4"};
static final String half_even[] = {"6", "2", "2", "1", "1", "-1", "-1", "-2", "-2", "-6",
"6", "-6", "6", "-6", "1", "-1", "5", "-5", "4", "-4"};
static final String unnecessary[] = {AE, AE, AE, AE, "1", "-1", AE, AE, AE, AE,
AE, AE, AE, AE, AE, AE, AE, AE, AE, AE};
public static void main(String[] args) {
basicTest();
roundTest(RoundingMode.UP, up);
roundTest(RoundingMode.DOWN, down);
roundTest(RoundingMode.CEILING, ceiling);
roundTest(RoundingMode.FLOOR, floor);
roundTest(RoundingMode.HALF_UP, half_up);
roundTest(RoundingMode.HALF_DOWN, half_down);
roundTest(RoundingMode.HALF_EVEN, half_even);
roundTest(RoundingMode.UNNECESSARY, unnecessary);
}
static void basicTest() {
NumberFormat nf = NumberFormat.getIntegerInstance();
if (nf.getRoundingMode() != RoundingMode.HALF_EVEN) {
throw new RuntimeException("default rounding is not HALF_EVEN");
}
try {
nf.setRoundingMode(null);
throw new RuntimeException(
"NullPointerException is not thrown by calling setRoundingMode(null)");
} catch (NullPointerException npe) {
// continue testing
}
ChoiceFormat cf = new ChoiceFormat("");
try {
cf.setRoundingMode(RoundingMode.HALF_EVEN);
throw new RuntimeException(
"UnsupportedOperationException is not thrown by calling setRoundingMode()");
} catch (UnsupportedOperationException uoe) {
// continue testing
}
try {
cf.getRoundingMode();
throw new RuntimeException(
"UnsupportedOperationException is not thrown by calling getRoundingMode()");
} catch (UnsupportedOperationException uoe) {
// continue testing
}
}
static void roundTest(RoundingMode rm, String[] expected) {
NumberFormat nf = NumberFormat.getIntegerInstance();
nf.setRoundingMode(rm);
if (nf.getRoundingMode() != rm) {
throw new RuntimeException("set rounding mode is not returned by get method");
}
for (int i = 0; i < src.length; i ++) {
String result = null;
try {
result = nf.parse(nf.format(src[i])).toString();
if (!result.equals(expected[i])) {
throw new RuntimeException("rounding test #"+i+" failed. mode: "+rm+" src: "+src[i]+" expected: "+expected[i]+" result: "+result);
}
} catch (ArithmeticException ae) {
if (expected[i].equals(AE)) {
continue;
} else {
result = AE;
throw new RuntimeException("rounding test #"+i+" failed. mode: "+rm+" src: "+src[i]+" expected: "+expected[i]+" result: "+result);
}
} catch (ParseException pe) {
throw new RuntimeException("ParseException ocurred.", pe);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 1997, 2025, 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 4266589 8031145 8164791 8316696 8368001
* @summary NumberFormat round trip testing of parsing and formatting.
* This test checks 4 factory instances per locale against ~20 numeric inputs.
* Samples ~1/4 of the available locales provided by NumberFormat.
* @key randomness
* @library /test/lib
* @build jdk.test.lib.RandomFactory
* @run junit NumberRoundTrip
*/
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
import jdk.test.lib.RandomFactory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* This class tests the round-trip behavior of NumberFormat, DecimalFormat, and DigitList.
* Round-trip behavior is tested by taking a numeric value and formatting it, then
* parsing the resulting string, and comparing this result with the original value.
* Two tests are applied: String preservation, and numeric preservation. String
* preservation is exact; numeric preservation is not. However, numeric preservation
* should extend to the few least-significant bits.
*/
public class NumberRoundTrip {
private static final Random RND = RandomFactory.getRandom();
@ParameterizedTest
@MethodSource
void testNumberFormatRoundTrip(NumberFormat fmt) {
fmt.setMaximumFractionDigits(Integer.MAX_VALUE);
Stream.concat(numbers.stream(), randomNumbers())
.forEach(num -> test(fmt, num));
}
private void test(NumberFormat fmt, Number num) {
String originalFormatted = fmt.format(num);
Number parsedNum = Assertions.assertDoesNotThrow(() -> fmt.parse(originalFormatted),
"Failed parse(format(%s))".formatted(num));
String parsedFormatted = fmt.format(parsedNum);
var equal = originalFormatted.equals(parsedFormatted);
// Try BigDecimal parsing, if not equal
if (!equal) {
var df = Assertions.assertInstanceOf(DecimalFormat.class, fmt);
df.setParseBigDecimal(true);
parsedNum = Assertions.assertDoesNotThrow(() -> fmt.parse(originalFormatted),
"Failed BigDecimal parse(format(%s))".formatted(num));
parsedFormatted = fmt.format(parsedNum);
df.setParseBigDecimal(false);
Assertions.assertEquals(originalFormatted, parsedFormatted,
"Failed to round-trip format(parse(format(%s)))".formatted(num));
}
// Numeric mismatch to the amount of 1e-14 is tolerable
var error = proportionalError(num, parsedNum);
Assertions.assertFalse(error > 1e-14,
"Round tripping %s caused numeric error: %s".formatted(num, error));
}
// Regular, number, currency, and percent instance per locale
private static Stream<Arguments> testNumberFormatRoundTrip() {
return Stream.concat(
// Default Locale
Stream.of(
Arguments.of(NumberFormat.getInstance()),
Arguments.of(NumberFormat.getNumberInstance()),
Arguments.of(NumberFormat.getCurrencyInstance()),
Arguments.of(NumberFormat.getPercentInstance())),
// ~1000 locales returned from provider.
// Too expensive to test all locales, so sample a reasonable amount
Arrays.stream(NumberFormat.getAvailableLocales())
.filter(_ -> RND.nextDouble() < .25)
.flatMap(loc -> Stream.of(
Arguments.of(NumberFormat.getInstance(loc)),
Arguments.of(NumberFormat.getNumberInstance(loc)),
Arguments.of(NumberFormat.getCurrencyInstance(loc)),
Arguments.of(NumberFormat.getPercentInstance(loc)))
)
);
}
// Fixed set of numbers to test each locale against
private static final List<Number> numbers = List.of(
Double.NaN,
Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY,
500,
0,
5555555555555555L,
55555555555555555L,
9223372036854775807L,
9223372036854775808.0,
-9223372036854775808L,
-9223372036854775809.0
);
// Compute fresh batch of random numbers per locale
private Stream<Number> randomNumbers() {
return Stream.of(
randomDouble(1),
randomDouble(10000),
Math.floor(randomDouble(10000)),
randomDouble(1e50),
randomDouble(1e-50),
randomDouble(1e100),
// The use of double d such that isInfinite(100d) causes the
// numeric test to fail with percent formats (bug 4266589).
// Largest double s.t. 100d < Inf: d=1.7976931348623156E306
randomDouble(1e306),
randomDouble(1e-323),
randomDouble(1e-100)
);
}
// Return a random value from -range..+range.
private static double randomDouble(double range) {
return RND.nextDouble(-range, range);
}
private static double proportionalError(Number a, Number b) {
double aa = a.doubleValue(), bb = b.doubleValue();
double error = aa - bb;
if (aa != 0 && bb != 0) error /= aa;
return Math.abs(error);
}
}

View file

@ -0,0 +1,421 @@
/*
* Copyright (c) 1997, 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 4122840 4135202 4408066 4838107 8008577 8174269
* @summary test NumberFormat
* @modules java.base/sun.util.resources
* jdk.localedata
* @compile -XDignore.symbol.file NumberTest.java
* @run junit NumberTest
*/
import java.util.*;
import java.text.*;
import sun.util.resources.LocaleData;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
public class NumberTest
{
// Test pattern handling
@Test
public void TestPatterns()
{
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
String pat[] = { "#.#", "#.", ".#", "#" };
String newpat[] = { "#0.#", "#0.", "#.0", "#" };
String num[] = { "0", "0.", ".0", "0" };
for (int i=0; i<pat.length; ++i)
{
DecimalFormat fmt = new DecimalFormat(pat[i], sym);
String newp = fmt.toPattern();
if (!newp.equals(newpat[i]))
fail("FAIL: Pattern " + pat[i] + " should transmute to " + newpat[i] +
"; " + newp + " seen instead");
String s = fmt.format(0);
if (!s.equals(num[i]))
{
fail("FAIL: Pattern " + pat[i] + " should format zero as " + num[i] +
"; " + s + " seen instead");
System.out.println("Min integer digits = " + fmt.getMinimumIntegerDigits());
}
}
}
// Test exponential pattern
@Test
public void TestExponential() {
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
String pat[] = { "0.####E0", "00.000E00", "##0.####E000", "0.###E0;[0.###E0]" };
double val[] = { 0.01234, 123456789, 1.23e300, -3.141592653e-271 };
long lval[] = { 0, -1, 1, 123456789 };
String valFormat[] = {
"1.234E-2", "1.2346E8", "1.23E300", "-3.1416E-271",
"12.340E-03", "12.346E07", "12.300E299", "-31.416E-272",
"12.34E-003", "123.4568E006", "1.23E300", "-314.1593E-273",
"1.234E-2", "1.235E8", "1.23E300", "[3.142E-271]"
};
String lvalFormat[] = {
"0E0", "-1E0", "1E0", "1.2346E8",
"00.000E00", "-10.000E-01", "10.000E-01", "12.346E07",
"0E000", "-1E000", "1E000", "123.4568E006",
"0E0", "[1E0]", "1E0", "1.235E8"
};
double valParse[] = {
0.01234, 123460000, 1.23E300, -3.1416E-271,
0.01234, 123460000, 1.23E300, -3.1416E-271,
0.01234, 123456800, 1.23E300, -3.141593E-271,
0.01234, 123500000, 1.23E300, -3.142E-271,
};
long lvalParse[] = {
0, -1, 1, 123460000,
0, -1, 1, 123460000,
0, -1, 1, 123456800,
0, -1, 1, 123500000,
};
int ival = 0, ilval = 0;
for (int p=0; p<pat.length; ++p) {
DecimalFormat fmt = new DecimalFormat(pat[p], sym);
System.out.println("Pattern \"" + pat[p] + "\" -toPattern-> \"" +
fmt.toPattern() + '"');
for (int v=0; v<val.length; ++v) {
String s = fmt.format(val[v]);
System.out.println(" Format " + val[v] + " -> " + escape(s));
if (!s.equals(valFormat[v+ival])) {
fail("FAIL: Expected " + valFormat[v+ival] +
", got " + s +
", pattern=" + fmt.toPattern());
}
ParsePosition pos = new ParsePosition(0);
Number a = fmt.parse(s, pos);
if (pos.getIndex() == s.length()) {
System.out.println(" Parse -> " + a);
if (a.doubleValue() != valParse[v+ival]) {
fail("FAIL: Expected " + valParse[v+ival] +
", got " + a.doubleValue() +
", pattern=" + fmt.toPattern());
}
} else {
fail(" FAIL: Partial parse (" + pos.getIndex() +
" chars) -> " + a);
}
}
for (int v=0; v<lval.length; ++v) {
String s = fmt.format(lval[v]);
System.out.println(" Format " + lval[v] + "L -> " + escape(s));
if (!s.equals(lvalFormat[v+ilval])) {
fail("ERROR: Expected " + lvalFormat[v+ilval] +
", got " + s +
", pattern=" + fmt.toPattern());
}
ParsePosition pos = new ParsePosition(0);
Number a = fmt.parse(s, pos);
if (pos.getIndex() == s.length()) {
System.out.println(" Parse -> " + a);
if (a.longValue() != lvalParse[v+ilval]) {
fail("FAIL: Expected " + lvalParse[v+ilval] +
", got " + a +
", pattern=" + fmt.toPattern());
}
} else {
fail(" FAIL: Partial parse (" + pos.getIndex() +
" chars) -> " + a);
}
}
ival += val.length;
ilval += lval.length;
}
}
// Test the handling of quotes
@Test
public void TestQuotes()
{
String pat;
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
DecimalFormat fmt = new DecimalFormat(pat = "a'fo''o'b#", sym);
String s = fmt.format(123);
System.out.println("Pattern \"" + pat + "\"");
System.out.println(" Format 123 -> " + escape(s));
if (!s.equals("afo'ob123")) fail("FAIL: Expected afo'ob123");
fmt = new DecimalFormat(pat = "a''b#", sym);
s = fmt.format(123);
System.out.println("Pattern \"" + pat + "\"");
System.out.println(" Format 123 -> " + escape(s));
if (!s.equals("a'b123")) fail("FAIL: Expected a'b123");
}
// Test the use of the currency sign
@Test
public void TestCurrencySign()
{
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
DecimalFormat fmt = new DecimalFormat("\u00A4#,##0.00;-\u00A4#,##0.00", sym);
// Can't test this properly until currency API goes public
// DecimalFormatSymbols sym = fmt.getDecimalFormatSymbols();
String s = fmt.format(1234.56);
System.out.println("Pattern \"" + fmt.toPattern() + "\"");
System.out.println(" Format " + 1234.56 + " -> " + escape(s));
if (!s.equals("$1,234.56")) fail("FAIL: Expected $1,234.56");
s = fmt.format(-1234.56);
System.out.println(" Format " + -1234.56 + " -> " + escape(s));
if (!s.equals("-$1,234.56")) fail("FAIL: Expected -$1,234.56");
fmt = new DecimalFormat("\u00A4\u00A4 #,##0.00;\u00A4\u00A4 -#,##0.00", sym);
s = fmt.format(1234.56);
System.out.println("Pattern \"" + fmt.toPattern() + "\"");
System.out.println(" Format " + 1234.56 + " -> " + escape(s));
if (!s.equals("USD 1,234.56")) fail("FAIL: Expected USD 1,234.56");
s = fmt.format(-1234.56);
System.out.println(" Format " + -1234.56 + " -> " + escape(s));
if (!s.equals("USD -1,234.56")) fail("FAIL: Expected USD -1,234.56");
}
static String escape(String s)
{
StringBuffer buf = new StringBuffer();
char HEX[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
for (int i=0; i<s.length(); ++i)
{
char c = s.charAt(i);
if (c <= (char)0x7F) buf.append(c);
else
{
buf.append("\\U");
buf.append(HEX[(c & 0xF000) >> 12]);
buf.append(HEX[(c & 0x0F00) >> 8]);
buf.append(HEX[(c & 0x00F0) >> 4]);
buf.append(HEX[c & 0x000F]);
}
}
return buf.toString();
}
// Test simple currency format
// Bug 4024941; this code used to throw a NumberFormat exception
@Test
public void TestCurrency() {
NumberFormat currencyFmt =
NumberFormat.getCurrencyInstance(Locale.CANADA_FRENCH);
String s = currencyFmt.format(1.50);
System.out.println("Un pauvre ici a..........." + s);
if (!s.equals("1,50\u00a0$")) {
fail("FAIL: Expected 1,50\u00a0$; got " + s + "; "+ dumpFmt(currencyFmt));
}
currencyFmt = NumberFormat.getCurrencyInstance(Locale.GERMANY);
s = currencyFmt.format(1.50);
System.out.println("Un pauvre en Allemagne a.." + s);
if (!s.equals("1,50\u00a0\u20AC")) {
fail("FAIL: Expected 1,50\u00a0\u20AC; got " + s + "; " + dumpFmt(currencyFmt));
}
currencyFmt = NumberFormat.getCurrencyInstance(Locale.FRANCE);
s = currencyFmt.format(1.50);
System.out.println("Un pauvre en France a....." + s);
if (!s.equals("1,50\u00a0\u20AC")) {
fail("FAIL: Expected 1,50\u00a0\u20AC; got " + s + "; " + dumpFmt(currencyFmt));
}
}
String dumpFmt(NumberFormat numfmt) {
DecimalFormat fmt = (DecimalFormat)numfmt;
StringBuffer buf = new StringBuffer();
buf.append("pattern \"");
buf.append(fmt.toPattern());
buf.append("\", currency \"");
buf.append(fmt.getDecimalFormatSymbols().getCurrencySymbol());
buf.append("\"");
return buf.toString();
}
// Test numeric parsing
// Bug 4059870
@Test
public void TestParse()
{
String arg = "0";
java.text.DecimalFormat format = new java.text.DecimalFormat("00");
try {
Number n = format.parse(arg);
System.out.println("parse(" + arg + ") = " + n);
if (n.doubleValue() != 0.0) fail("FAIL: Expected 0");
} catch (Exception e) { fail("Exception caught: " + e); }
}
// Test rounding
@Test
public void TestRounding487() {
NumberFormat nf = NumberFormat.getInstance(Locale.US);
roundingTest(nf, 0.00159999, 4, "0.0016");
roundingTest(nf, 0.00995, 4, "0.01");
roundingTest(nf, 12.7995, 3, "12.8");
roundingTest(nf, 12.4999, 0, "12");
roundingTest(nf, -19.5, 0, "-20");
}
void roundingTest(NumberFormat nf, double x, int maxFractionDigits, String expected) {
nf.setMaximumFractionDigits(maxFractionDigits);
String out = nf.format(x);
System.out.println("" + x + " formats with " + maxFractionDigits + " fractional digits to " + out);
if (!out.equals(expected)) {
fail("FAIL: Expected " + expected + ", got " + out);
}
}
/**
* Bug 4135202
* DecimalFormat should recognize not only Latin digits 0-9 (\u0030-\u0039)
* but also various other ranges of Unicode digits, such as Arabic
* digits \u0660-\u0669 and Devanagari digits \u0966-\u096F, to name
* a couple.
* @see java.lang.Character#isDigit(char)
*/
@Test
public void TestUnicodeDigits() {
char[] zeros = {
0x0030, // ISO-LATIN-1 digits ('0' through '9')
0x0660, // Arabic-Indic digits
0x06F0, // Extended Arabic-Indic digits
0x0966, // Devanagari digits
0x09E6, // Bengali digits
0x0A66, // Gurmukhi digits
0x0AE6, // Gujarati digits
0x0B66, // Oriya digits
0x0BE6, // Tamil digits
0x0C66, // Telugu digits
0x0CE6, // Kannada digits
0x0D66, // Malayalam digits
0x0E50, // Thai digits
0x0ED0, // Lao digits
0x0F20, // Tibetan digits
0xFF10, // Fullwidth digits
};
NumberFormat format = NumberFormat.getInstance();
for (int i=0; i<zeros.length; ++i) {
char zero = zeros[i];
StringBuffer buf = new StringBuffer();
buf.append((char)(zero+3));
buf.append((char)(zero+1));
buf.append((char)(zero+4));
int n = -1;
try {
n = format.parse(buf.toString()).intValue();
}
catch (ParseException e) { n = -2; }
if (n != 314)
fail("Can't parse Unicode " + Integer.toHexString(zero) + " as digit (" + n + ")");
else
System.out.println("Parse digit " + Integer.toHexString(zero) + " ok");
}
}
/**
* Bug 4122840
* Make sure that the currency symbol is not hard-coded in any locale.
*/
@Test
public void TestCurrencySubstitution() {
final String SYM = "<currency>";
final String INTL_SYM = "<intl.currency>";
Locale[] locales = NumberFormat.getAvailableLocales();
for (int i=0; i<locales.length; ++i) {
NumberFormat nf = NumberFormat.getCurrencyInstance(locales[i]);
if (nf instanceof DecimalFormat) {
DecimalFormat df = (DecimalFormat)nf;
String genericPos = df.format(1234.5678);
String genericNeg = df.format(-1234.5678);
DecimalFormatSymbols sym = df.getDecimalFormatSymbols();
sym.setCurrencySymbol(SYM);
sym.setInternationalCurrencySymbol(INTL_SYM);
// We have to make a new DecimalFormat from scratch in order
// to make the new symbols 'take'. This may be a bug or
// design flaw in DecimalFormat.
String[] patterns = LocaleData.getBundle("sun.text.resources.FormatData", locales[i])
.getStringArray("NumberPatterns");
df = new DecimalFormat(patterns[1 /*CURRENCYSTYLE*/], sym);
String customPos = df.format(1234.5678);
String customNeg = df.format(-1234.5678);
if (genericPos.equals(customPos) || genericNeg.equals(customNeg)) {
fail("FAIL: " + locales[i] +
" not using currency symbol substitution: " + genericPos);
}
else {
if (customPos.indexOf(SYM) >= 0) {
if (customNeg.indexOf(INTL_SYM) >= 0)
fail("Fail: Positive and negative patterns use different symbols");
else
System.out.println("Ok: " + locales[i] +
" uses currency symbol: " + genericPos +
", " + customPos);
}
else if (customPos.indexOf(INTL_SYM) >= 0) {
if (customNeg.indexOf(SYM) >= 0)
fail("Fail: Positive and negative patterns use different symbols");
else
System.out.println("Ok: " + locales[i] +
" uses intl. currency symbol: " + genericPos +
", " + customPos);
}
else {
fail("FAIL: " + locales[i] +
" contains no currency symbol (impossible!)");
}
}
}
else System.out.println("Skipping " + locales[i] + "; not a DecimalFormat");
}
}
@Test
public void TestIntegerFormat() throws ParseException {
NumberFormat format = NumberFormat.getIntegerInstance(Locale.GERMANY);
float[] formatInput = { 12345.67f, -12345.67f, -0, 0 };
String[] formatExpected = { "12.346", "-12.346", "0", "0" };
for (int i = 0; i < formatInput.length; i++) {
String result = format.format(formatInput[i]);
if (!result.equals(formatExpected[i])) {
fail("FAIL: Expected " + formatExpected[i] + ", got " + result);
}
}
String[] parseInput = { "0", "-0", "12.345,67", "-12.345,67" };
float[] parseExpected = { 0, 0, 12345, -12345 };
for (int i = 0; i < parseInput.length; i++) {
float result = format.parse(parseInput[i]).floatValue();
if (result != parseExpected[i]) {
fail("FAIL: Expected " + parseExpected[i] + ", got " + result);
}
}
}
}

View file

@ -0,0 +1,277 @@
/*
* Copyright (c) 1997, 2025, 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.
*/
/*
(C) Copyright Taligent, Inc. 1996 - All Rights Reserved
(C) Copyright IBM Corp. 1996 - All Rights Reserved
The original version of this source code and documentation is copyrighted and
owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These materials are
provided under terms of a License Agreement between Taligent and Sun. This
technology is protected by multiple US and International patents. This notice and
attribution to Taligent may not be removed.
Taligent is a registered trademark of Taligent, Inc.
*/
/*
* @test
* @bug 4109023 4153060 4153061 8366400
* @summary test ParsePosition and FieldPosition
* @run junit PositionTest
*/
import org.junit.jupiter.api.Test;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
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.fail;
public class PositionTest {
// Parsing text which contains un-parseable data, but the index
// begins at the valid portion. Ensure PP is properly updated.
@Test
public void modifiedPositionTest() {
var df = new DecimalFormat("YY#");
df.setStrict(false); // Lenient by default, set for test explicitness
var pp = new ParsePosition(9);
assertEquals(123L, assertDoesNotThrow(() -> df.parse("FOOBARBAZYY123", pp)));
assertEquals(-1, pp.getErrorIndex());
assertEquals(14, pp.getIndex());
}
// Clearly invalid index value that could not work under any scenarios
// Specifically, ensuring no SIOOBE during affix matching
@Test
public void invalidPositionParseTest() {
var df = new DecimalFormat();
df.setStrict(false); // Lenient by default, set for test explicitness
assertNull(assertDoesNotThrow(() -> df.parse("1", new ParsePosition(-1))));
assertNull(assertDoesNotThrow(() -> df.parse("1", new ParsePosition(Integer.MAX_VALUE))));
}
// When prefix matching, position + affix length is greater than parsed String length
// Ensure we do not index out of bounds of the length of the parsed String
@Test
public void prefixMatchingTest() {
var df = new DecimalFormat("ZZZ#;YYY#");
df.setStrict(false); // Lenient by default, set for test explicitness
// 0 + 3 > 2 = (pos + prefix > text)
assertNull(assertDoesNotThrow(() -> df.parse("Z1", new ParsePosition(0))));
assertNull(assertDoesNotThrow(() -> df.parse("Y1", new ParsePosition(0))));
}
// When suffix matching, position + affix length is greater than parsed String length
// Ensure we do not index out of bounds of the length of the parsed String
@Test
public void suffixMatchingTest() {
var df = new DecimalFormat("#ZZ;#YY");
df.setStrict(false); // Lenient by default, set for test explicitness
// Matches prefix properly first. Then 3 + 2 > 4 = (pos + suffix > text)
assertNull(assertDoesNotThrow(() -> df.parse("123Z", new ParsePosition(0))));
assertNull(assertDoesNotThrow(() -> df.parse("123Y", new ParsePosition(0))));
}
@Test
public void TestParsePosition() {
ParsePosition pp1 = new ParsePosition(0);
if (pp1.getIndex() == 0) {
System.out.println("PP constructor() tested.");
}else{
fail("*** PP getIndex or constructor() result");
}
{
int to = 5;
ParsePosition pp2 = new ParsePosition ( to );
if (pp2.getIndex() == 5) {
System.out.println("PP getIndex and constructor(TextOffset) tested.");
}else{
fail("*** PP getIndex or constructor(TextOffset) result");
}
pp2.setIndex( 3 );
if (pp2.getIndex() == 3) {
System.out.println("PP setIndex tested.");
}else{
fail("*** PP getIndex or setIndex result");
}
}
ParsePosition pp2, pp3;
pp2 = new ParsePosition( 3 );
pp3 = new ParsePosition( 5 );
ParsePosition pp4 = new ParsePosition(5);
if (! pp2.equals(pp3)) {
System.out.println("PP not equals tested.");
}else{
fail("*** PP not equals fails");
}
if (pp3.equals(pp4)) {
System.out.println("PP equals tested.");
}else{
fail("*** PP equals fails (" + pp3.getIndex() + " != " + pp4.getIndex() + ")");
}
ParsePosition pp5;
pp5 = pp4;
if (pp4.equals(pp5)) {
System.out.println("PP operator= tested.");
}else{
fail("*** PP operator= operator== or operator != result");
}
}
@Test
public void TestFieldPosition() {
FieldPosition fp = new FieldPosition( 7 );
if (fp.getField() == 7) {
System.out.println("FP constructor(int) and getField tested.");
}else{
fail("*** FP constructor(int) or getField");
}
FieldPosition fph = new FieldPosition( 3 );
if ( fph.getField() != 3) fail("*** FP getField or heap constr.");
boolean err1 = false;
boolean err2 = false;
boolean err3 = false;
// for (long i = -50; i < 50; i++ ) {
// fp.setField( i+8 );
// fp.setBeginIndex( i+6 );
// fp.setEndIndex( i+7 );
// if (fp.getField() != i+8) err1 = true;
// if (fp.getBeginIndex() != i+6) err2 = true;
// if (fp.getEndIndex() != i+7) err3 = true;
// }
if (!err1) {
System.out.println("FP setField and getField tested.");
}else{
fail("*** FP setField or getField");
}
if (!err2) {
System.out.println("FP setBeginIndex and getBeginIndex tested.");
}else{
fail("*** FP setBeginIndex or getBeginIndex");
}
if (!err3) {
System.out.println("FP setEndIndex and getEndIndex tested.");
}else{
fail("*** FP setEndIndex or getEndIndex");
}
System.out.println("");
}
@Test
public void TestFieldPosition_example() {
//***** no error detection yet !!!!!!!
//***** this test is for compiler checks and visual verification only.
double doubleNum[] = { 123456789.0, -12345678.9, 1234567.89, -123456.789,
12345.6789, -1234.56789, 123.456789, -12.3456789, 1.23456789};
int dNumSize = doubleNum.length;
DecimalFormat fmt = (DecimalFormat) NumberFormat.getInstance();
fmt.setDecimalSeparatorAlwaysShown(true);
final int tempLen = 20;
StringBuffer temp;
for (int i=0; i<dNumSize; i++) {
temp = new StringBuffer(); // Get new buffer
FieldPosition pos = new FieldPosition(NumberFormat.INTEGER_FIELD);
StringBuffer buf = new StringBuffer();
//char fmtText[tempLen];
//ToCharString(fmt->format(doubleNum[i], buf, pos), fmtText);
StringBuffer res = fmt.format(doubleNum[i], buf, pos);
int tempOffset = (tempLen <= (tempLen - pos.getEndIndex())) ?
tempLen : (tempLen - pos.getEndIndex());
for (int j=0; j<tempOffset; j++) temp.append('='); // initialize
//cout << temp << fmtText << endl;
System.out.println("FP " + temp + res);
}
System.out.println("");
}
/* @bug 4109023
* Need to override ParsePosition.equals and FieldPosition.equals.
*/
@Test
public void Test4109023()
{
ParsePosition p = new ParsePosition(3);
ParsePosition p2 = new ParsePosition(3);
if (!p.equals(p2))
fail("Error : ParsePosition.equals() failed");
FieldPosition fp = new FieldPosition(2);
FieldPosition fp2 = new FieldPosition(2);
if (!fp.equals(fp2))
fail("Error : FieldPosition.equals() failed");
}
/**
* @bug 4153060
* ParsePosition.hashCode() returns different values on equal objects.
*/
@Test
public void Test4153060() {
ParsePosition p = new ParsePosition(53);
ParsePosition q = new ParsePosition(53);
if (!p.equals(q)) {
fail("" + p + " and " + q + " are not equal and should be");
}
if (p.hashCode() != q.hashCode()) {
fail("ParsePosition.hashCode() different for equal objects");
} else {
System.out.println("hashCode(" + p + ") = " + p.hashCode());
}
}
/**
* @bug 4153061
* FieldPosition.hashCode() returns different values on equal objects.
*/
@Test
public void Test4153061() {
FieldPosition p = new FieldPosition(53);
FieldPosition q = new FieldPosition(53);
if (!p.equals(q)) {
fail("" + p + " and " + q + " are not equal and should be");
}
if (p.hashCode() != q.hashCode()) {
fail("FieldPosition.hashCode() different for equal objects");
} else {
System.out.println("hashCode(" + p + ") = " + p.hashCode());
}
}
}

View file

@ -0,0 +1,94 @@
/*
* 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
* @summary Unit test for the isStrict() and setStrict() parsing related methods
* @run junit StrictMethodsTest
*/
import org.junit.jupiter.api.Test;
import java.text.CompactNumberFormat;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class StrictMethodsTest {
// Check that DecimalFormat implements isStrict()/setStrict()
// Ensure that the default value is false, and can be set to true via API
@Test
public void decimalFormatTest() {
DecimalFormat dFmt = (DecimalFormat) NumberFormat.getInstance();
assertFalse(dFmt.isStrict());
dFmt.setStrict(true);
assertTrue(dFmt.isStrict());
}
// Check that CompactNumberFormat implements isStrict()/setStrict()
// Ensure that the default value is false, and can be set to true via API
@Test
public void compactFormatTest() {
CompactNumberFormat cFmt = (CompactNumberFormat) NumberFormat.getCompactNumberInstance();
assertFalse(cFmt.isStrict());
cFmt.setStrict(true);
assertTrue(cFmt.isStrict());
}
// Check that NumberFormat throws exception for isStrict()/setStrict()
// when subclass does not implement said methods
@Test
public void numberFormatTest() {
FooFormat fmt = new FooFormat();
assertThrows(UnsupportedOperationException.class, fmt::isStrict);
assertThrows(UnsupportedOperationException.class, () -> fmt.setStrict(false));
}
// Dummy NumberFormat class to check that isStrict() and setStrict()
// are not implemented by default
private static class FooFormat extends NumberFormat {
// Provide overrides for abstract methods
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
return null;
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
return null;
}
@Override
public Number parse(String source, ParsePosition parsePosition) {
return null;
}
}
}

View file

@ -0,0 +1,618 @@
/*
* 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();
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 1999, 2016, 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 4201262
* @summary Make sure that DecimalFormatSymbols.getPatternSeparator returns ';' in ja locale.
*/
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
public class TestgetPatternSeparator_ja {
public static void main(String[] argv) throws Exception {
DecimalFormat df = (DecimalFormat)NumberFormat.getInstance(Locale.JAPAN);
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
if (dfs.getPatternSeparator() != ';') {
throw new Exception("DecimalFormatSymbols.getPatternSeparator doesn't return ';' in ja locale");
}
}
}