From 7b7a2143077b09a3c79873d7e7fc49ad3ec56246 Mon Sep 17 00:00:00 2001 From: Travis Keep Date: Mon, 1 Jun 2015 20:28:39 +0000 Subject: [PATCH] ICU-11692 DecimalFormat data driven tests for JAVA. X-SVN-Rev: 37484 --- .../data/numberformattestspecification.txt | 758 ++++++++++++++++++ .../DataDrivenNumberFormatTestSuite.java | 327 ++++++++ .../icu/dev/test/format/NumberFormatTest.java | 359 +++++++++ .../test/format/NumberFormatTestTuple.java | 501 ++++++++++++ 4 files changed, 1945 insertions(+) create mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt create mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DataDrivenNumberFormatTestSuite.java create mode 100644 icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTestTuple.java diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt new file mode 100644 index 00000000000..e2c6518de10 --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt @@ -0,0 +1,758 @@ +// Copyright (C) 2015, International Business Machines +// Corporation and others. All Rights Reserved. +// +// This file is divided into test suites separated by whitespace. Each test +// suite starts with the name of the test followed by global field settings +// for that test suite. After the global settings, comes "begin", the +// per-test field names, and finally the test specific field values, 1 test +// per line. +// For more information on the format of this file, including all the available +// field names, please see +// https://docs.google.com/document/d/1T2P0p953_Lh1pRwo-5CuPVrHlIBa_wcXElG-Hhg_WHM/edit?usp=sharing +test basic patterns +set locale fr_FR +set format 1234.567 +begin +pattern output +#,##0.## 1\u00a0234,57 +0.## 1234,57 +0 1235 +#,##0.### 1\u00a0234,567 +###0.###### 1234,567 +###0.0000# 1234,5670 +00000.0000 01234,5670 +#,##0.00 \u00a4 1\u00a0234,57 \u20ac +'tick''bitten '0.00 tick'bitten 1234,57 +'tick' 'bitten '0.00 tick bitten 1234,57 + + +test minimum and maximum fraction digits +set locale en +set minIntegerDigits 2 +set maxIntegerDigits 4 +set minFractionDigits 3 +set maxFractionDigits 5 +begin +format output +6 06.000 +6.000005 06.000 +6.000006 06.00001 +12 12.000 +12345 2345.000 +72.1234 72.1234 + +test rounding +set locale fr +begin +pattern format output breaks +0.5 1.25 1,0 K +0.5 1.75 2,0 K +0.5 -1.25 -1,0 K +00.5 -1.75 -02,0 K +4 2.0 0 K +4 6.0 8 K +4 10.0 8 K +2.70 99.0 99,90 K +2.73 272.0 273,00 K +#,#3.70 104.0 1\u00a003,60 K + +test significant digits +set locale en +set pattern #,#@,@### +begin +format output breaks +7 7.0 K +23 23 K +100 100 K +1000 1000 K +10000 1,0000 K +10001 1,0001 K +10001.5 1,0002 K +1234567 1,23,4600 K +-1234567 -1,23,4600 K +3.14159 3.1416 K + +test scientific notation +set locale fr +begin +pattern format output breaks +0.00E0 12345 1,23E4 +000.00E0 12300 123,00E2 +000.0#E0 12300 123,0E2 +000.0#E0 12300.1 123,0E2 +000.0#E0 12301.0 123,01E2 +// JDK does not support exponent signs +000.0#E+00 12301.0 123,01E+02 K +// JDK gives 12,345E3. JDK seems to include the hashes in significant digits +##0.00E0 12345 12,3E3 K +// JDK gives 12,3001E3 +##0.0000E0 12300.1 12,300E3 K +// JDK gives 12,3001E3 +##0.000#E0 12300.1 12,30E3 K +##0.000#E0 12301 12,301E3 +// This should round to nearest 0.05E4 but instead round to nearest 0.05 +0.05E0 12301.2 1,25E4 CJK +##0.000#E0 0.17 170,0E-3 +// JDK doesn't support significant digits in exponents +@@@E0 6235 6,24E3 K +@@@#E0 6200 6,20E3 K +@@@#E0 6201 6,201E3 K +@@@#E0 6201.7 6,202E3 K +@@@#E00 6201.7 6,202E03 K +@@@#E+00 6201.7 6,202E+03 K + +test percents +set locale fr +begin +pattern format output breaks +0.0% 0.573 57,3% +%0.0 0.573 %57,3 +p%p0.0 0.573 p%p57,3 +p'%'p0.0 0.573 p%p0,6 +%@@@@ 0.0326 %3,260 K +%#,@@@ 15.43 %1\u00a0540 K +// JDK does not support rounding increments +%#,##4.1 16.55 %1\u00a0656,4 K +// JDK gives %16,255E3 +%##0.00E0 162.55 %16,3E3 K + +test permille +set locale fr +begin +pattern format output breaks +0.0\u2030 0.573 573,0‰ +\u20300.0 0.573 \u2030573,0 +p\u2030p0.0 0.573 p\u2030p573,0 +p'\u2030'p0.0 0.573 p\u2030p0,6 +\u2030@@@@ 0.0326 \u203032,60 K +\u2030#,@@@ 15.43 \u203015\u00a0400 K +\u2030#,##4.1 16.55 \u203016\u00a0551,7 K +// JDK gives \u2030162,55E3 +\u2030##0.00E0 162.55 \u2030163E3 K + +test padding +set locale fr_FR +begin +pattern format output breaks +// JDK does not seem to support padding +$**####,##0 1234 $***1\u00a0234 K +*x$####,##0 1234 xxx$1\u00a0234 K +####,##0*x$ 1234 1\u00a0234xxx$ K +####,##0$*x 1234 1\u00a0234$xxx K +// JDK doesn't seem to handle suffixes correctly dropping the 'nx' entirely +####,##0$*x;ne#n -1234 ne1\u00a0234nx K +####,##0$*x;n#'*' -1234 n1\u00a0234*xx K +*y%4.2###### 4.33 yyyy%432,6 K +// In C & J ICU adds padding as if 'EUR' is only 2 chars (2 * 0xa4) +\u00a4\u00a4 **####0.00 433.0 EUR *433,00 CJK +// In C & J ICU adds padding as if 'EUR' is only 2 chars (2 * 0xa4) +\u00a4\u00a4 **#######0 433.0 EUR *433,00 CJK + +test padding and currencies +begin +locale currency pattern format output breaks +// In J, JPY is considered 2 char (2 * 0xa4) even though padding is done +// after prefix. In C this test works. +fr JPY \u00a4\u00a4 **#######0 433.22 JPY ****433 JK +// JDK doesn't correct rounding for currency, shows USD (433 +en USD \u00a4\u00a4 **#######0;\u00a4\u00a4 (#) -433.22 USD (433.22) K + +test currencies +set locale fr +set format 1234.567 +begin +pattern currency output breaks +// JDK gives shows EUR instead of the euro symbol in this case +#,##0.00 \u00a4 EUR 1\u00a0234,57 \u20ac K +// JDK gives 1\u00A0234,57. JDK doesn't seem to correct rounding +// based on currency. +#,##0.00 \u00a4 JPY 1\u00a0235 JPY K + +test prefixes and suffixes +set locale en +set pattern 0.00+;(#) +begin +format output breaks +7 7.00+ +// JDK does not support negative suffixes +-3.5 (3.50) K + +test minimum grouping digits +set locale en +set pattern #,##0 +set minGroupingDigits 2 +begin +format output breaks +// min grouping digits not supported in any existing implementation +// but is supported in the new C code. +1000 1000 CJK +10000 10,000 +100000 100,000 +1000000 1,000,000 + +test min max fraction digits +set locale en +set pattern #,##0.### +set format 1234.567 +begin +minFractionDigits maxFractionDigits output +0 0 1,235 +0 2 1,234.57 +4 5 1,234.5670 + +test min max integer digits +set locale en +set pattern #,##0.### +set format 1234.567 +begin +minIntegerDigits maxIntegerDigits output +0 0 .567 +0 3 234.567 +5 5 01,234.567 + +test min max fraction digits scientific +set locale en +set pattern #E0 +set format 299792458.0 +begin +minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output breaks +// JDK gives .3E9 instead of unlimited precision. +0 1 0 0 2.99792458E8 K +1 1 0 0 3E8 +// JDK gives E0 instead of allowing for unlimited precision +0 0 0 0 2.99792458E8 K +// JDK gives .299792E9 +0 1 0 5 2.9979E8 K +// JDK gives 300E6 +0 3 0 0 299.792458E6 K +// JDK gives 299.8E6 (maybe maxInt + maxFrac instead of minInt + maxFrac)? +0 3 0 1 300E6 K +// JDK gives 299.7925E6 +2 3 0 4 299.792E6 K +// JDK gives 299.79246E6 +2 3 0 5 299.7925E6 K +3 3 0 5 299.79246E6 +3 3 0 4 299.7925E6 +2 2 0 3 29.979E7 +4 4 0 0 2998E5 +0 0 1 5 .29979E9 +// JDK gives E0 +0 0 1 0 2.99792458E8 K +// JDK gives .2998E9 +0 0 0 4 2.998E8 K +// JDK uses 8 + 6 for significant digits instead of 2 + 6 +2 8 1 6 2.9979246E8 K +// Treat max int digits > 8 as being the same as min int digits. +// This behavior is not spelled out in the specification. +// JDK fails here because it tries to use 9 + 6 = 15 sig digits. +2 9 1 6 29.979246E7 K + +test significant digits scientific +set locale en +set pattern #E0 +set format 290000000.0 +begin +minSigDigits maxSigDigits output breaks +0 1 3E8 K +0 2 2.9E8 K +0 3 2.9E8 K +1 1 3E8 K +1 2 2.9E8 K +1 3 2.9E8 K +2 2 2.9E8 K +2 3 2.9E8 K +3 3 2.90E8 K +3 4 2.90E8 K + +test min max fraction digits scientific 2 +set locale en +set pattern #E0 +set format 29979245.0 +begin +minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output breaks +// JDK gives E0 +0 0 0 0 2.9979245E7 K +// JDK gives .3E8 +0 1 0 0 2.9979245E7 K +// JDK gives 2998E4. +0 4 0 0 2997.9245E4 K + +test ticket 11524 +set locale en +set pattern #,##0.### +begin +format maxIntegerDigits output +123 1 3 +123 -2147483648 0 +12345 1 5 +12345 -2147483648 0 +5.3 1 5.3 +5.3 -2147483648 .3 + +test patterns with zero +set locale en +set format 0 +begin +pattern output +#.# 0 +#. 0. +.# .0 +# 0 +00.000E00 00.000E00 +0.####E0 0E0 +##0.######E000 0E000 + +test significant digits manually set +set locale en_US +set pattern 0.0 +set useSigDigits 1 +set minSigDigits 3 +set maxSigDigits 5 +begin +format output breaks +0.0012 0.00120 K +0.00123 0.00123 K +0.001234 0.001234 K +0.0012345 0.0012345 K +0.00123456 0.0012346 K +-43 -43.0 K +-43.7 -43.7 K +-43.76 -43.76 K +-43.762 -43.762 K +-43.7626 -43.763 K + +test grouping used setters +set locale en_US +set pattern #,##0 +set format 12345 +begin +output useGrouping +12,345 +12,345 1 +12345 0 + +test grouping setters +set locale en_US +set pattern 0 +set format 123456789 +set useGrouping 1 +begin +output grouping breaks grouping2 minGroupingDigits +1,2345,6789 4 +1,23,45,6789 4 K 2 +1,23,45,6789 4 K 2 2 +123,456789 6 K 6 3 +123456789 6 CJK 6 4 + +test multiplier setters +set locale en_US +begin +format multiplier output breaks +23 -12 -276 +23 -1 -23 +// ICU4J and JDK throw exception on zero multiplier. ICU4C does not. +23 0 23 JK +23 1 23 +23 12 276 +-23 12 -276 +-23 -12 276 + +test rounding setters +set locale en_US +set pattern 0.0# +set roundingIncrement 0.25 +begin +format output breaks +-0.35 -0.25 K +0.35 0.25 K +0.39 0.5 K +0.62 0.5 K +0.63 0.75 K + +test padding setters +set locale en_US +set pattern bill0 +set format 1357 +begin +padCharacter formatWidth output breaks +* 8 bill1357 K +* 9 *bill1357 K +^ 10 ^^bill1357 K + +test use scientific setter +set locale en_US +set pattern 0.00 +set format 186283 +begin +output breaks useScientific +186283.00 +1.86E5 K 1 +186283.00 K 0 + +test rounding mode setters +set locale en_US +set pattern 0.# +set roundingIncrement 0.5 +begin +format roundingMode output breaks +1.24 halfUp 1 K +1.25 halfUp 1.5 K +1.25 halfDown 1 K +1.26 halfDown 1.5 K +1.25 halfEven 1 K +-1.01 up -1.5 K +-1.49 down -1 K +1.01 up 1.5 K +1.49 down 1 K +-1.01 ceiling -1 K +-1.49 floor -1.5 K + +test currency usage setters +// TODO: Find a country and currency where standard and cash differ +set locale CH +set currency CHF +set pattern \u00a4\u00a4 0 +begin +format currencyUsage output breaks +0.37 standard CHF 0.37 K +// TODO: Get the rounding data into ICU4C and ICU4J +0.37 cash CHF 0.35 CJK + +test exponent parameter setters +set locale en_US +set pattern 0.##E0 +set format 299792458 +begin +decimalSeparatorAlwaysShown exponentSignAlwaysShown minimumExponentDigits output breaks +0 0 2 3E08 K +0 1 3 3E+008 K +// ICU DecimalFormat both C and J do not honor decimalSeparatorAlwaysShown +// for scientific notation. But JDK DecimalFormat does honor +// decimalSeparatorAlwaysShown K=JDK; C=ICU4C; J=ICU4J +// See ticket 11621 +1 0 2 3.E08 CJK +1 1 3 3.E+008 CJK +1 0 1 3.E8 CJ +0 0 1 3E8 + +test exponent decimalSeparatorAlwaysShown default +set locale en_US +set pattern 0.##E0 +begin +format output breaks decimalSeparatorAlwaysShown +// decimalSeparatorAlwaysShown off by default +299792458 3E8 +299000000 2.99E8 +299792458 3.E8 CJ 1 + +test pad position setters +set locale en_US +set pattern **[#####0.##];((#)) +begin +format padPosition output breaks +12.34 beforePrefix ****[12.34] K +12.34 afterPrefix [****12.34] K +12.34 beforeSuffix [12.34****] K +12.34 afterSuffix [12.34]**** K +-12.34 beforePrefix **((12.34)) K +-12.34 afterPrefix ((**12.34)) K +-12.34 beforeSuffix ((12.34**)) K +-12.34 afterSuffix ((12.34))** K + +test affix setters +set locale fr_FR +set currency EUR +set pattern 0.00 +begin +format positivePrefix positiveSuffix negativePrefix negativeSuffix output +12.34 % %12,34 +12.34 \u00a4\u00a4 12,34\u00a4\u00a4 +12.34 - + 12,34 +-12.34 - + -12,34+ +-12.34 \u00a4 \u00a412,34 +12.34 \u00a4 12,34 +-12.34 % 12,34% +12.34 % 12,34 + +test more affix setters +set locale fr_FR +set pattern %0.00 +begin +format positivePrefix negativePrefix output +0.648 booya cooya booya64,80 + +test nan and infinity +set locale en_US +set pattern [0.00];(#) +begin +format output breaks +Inf [\u221e] K +-Inf (\u221e) K +NaN NaN K + +test nan and infinity with multiplication +set locale en +set multiplier 100 +begin +format output breaks +Inf \u221e +-Inf -\u221e +NaN NaN K + +test nan and infinity with padding +set locale en_US +set pattern $$$0.00$ +set formatWidth 7 +begin +format padPosition output breaks +Inf beforePrefix $$$\u221e$ K +Inf afterPrefix $$$ \u221e$ K +Inf beforeSuffix $$$\u221e $ K +Inf afterSuffix $$$\u221e$ K +NaN beforePrefix NaN K +NaN afterPrefix NaN K +NaN beforeSuffix NaN K +NaN afterSuffix NaN K + +test apply localized patterns +begin +locale localizedPattern format output breaks +en 0% 0.4376 44% +// This next test breaks JDK. JDK doesn't multiply by 100. +fa \u06f0\u066a 0.4376 \u06f4\u06f4\u066a K + +test toPattern +set locale en +begin +pattern toPattern breaks +// JDK doesn't support any patterns with padding or both negative prefix and suffix +// Breaks ICU4C and ICU4J See ticket 11671 +**0,000 **0,000 CJK +**##0,000 **##0,000 K +**###0,000 **###0,000 K +**####0,000 **#,##0,000 K +###,000. #,000. +0,000 #0,000 +.00 #.00 +000 #000 +000,000 #,000,000 +pp#,000 pp#,000 +00.## #00.## +#,#00.025 #,#00.025 +// No secondary grouping in JDK +#,##,###.02500 #,##,###.02500 K +pp#,000;(#) pp#,000;(#,000) K +**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) K +// No significant digits in JDK +@@### @@### K +@,@#,### @,@#,### K +0.00E0 0.00E0 +@@@##E0 @@@##E0 K +###0.00#E0 ###0.00#E0 +##00.00#E0 ##00.00#E0 +0.00E+00 0.00E+00 K +000.00E00 000.00E00 +###0.00#E00 ###0.00#E00 + +test parse +set locale en +set pattern +#,##0.0###;(#) +begin +parse output breaks ++5,347.25 5347.25 ++5,347,.25 5347.25 ++5,347, 5347 ++5347,,, 5347 ++5347,,,d8 5347 +(5,347.25) -5347.25 +// J requires prefix and suffix for lenient parsing, but C doesn't +5,347.25 5347.25 JK +(5,347.25 -5347.25 J +-5,347.25 fail ++3.52E4 35200 +(34.8E-3) -0.0348 +// JDK stops parsing at the spaces. JDK doesn't see space as a grouping separator +(34 25E-1) -342.5 K +(34,,25E-1) -342.5 +// J doesn't allow trailing separators before E but C does +(34,,25,E-1) -342.5 J +(34 25 E-1) -342.5 JK +(34,,25 E-1) -3425 J +// Spaces are not allowed after exponent symbol +// C parses up to the E but J bails +(34 25E -1) -3425 JK ++3.52EE4 3.52 ++1,234,567.8901 1234567.8901 ++1,23,4567.8901 1234567.8901 ++1,23,4567.89,01 1234567.89 ++1,23,456.78.9 123456.78 ++12.34,56 12.34 ++79,,20,3 79203 ++79 20 3 79203 K +// Parsing stops at comma as it is different from other separators ++79 20,3 7920 K ++79,,20 3 7920 ++ 79 79 K ++,79,,20,3 79203 ++7920d3 7920 +// Whitespace immediately after prefix doesn't count as digit separator +// in C but is does in J ++ ,79,,20,3 79203 JK +( 19 45) -1945 K +// C allows trailing separators when there is a prefix and suffix. +// J allows trailing separators only when there is just a prefix. +// In this case, J just bails +( 19 45 ) -1945 JK +(,,19,45) -1945 +// C parses to the space, but J bails +(,,19 45) -19 J +// J bails b/c comma different separator than space. C doesn't treat leading spaces +// as a separator. +( 19,45) -1945 JK +// J bails. Doesn't allow trailing separators when there is prefix and suffix. +(,,19,45,) -1945 J +// J bails on next 4 because J doesn't allow letters inside prefix and suffix. +// C will parse up to the letter. +(,,19,45,d1) -1945 J +(,,19,45d1) -1945 J +( 19 45 d1) -1945 JK +( 19 45d1) -1945 JK +// J does allow trailing separator before a decimal point +(19,45,.25) -1945.25 +// 2nd decimal points are ignored ++4.12.926 4.12 + +test parse suffix +set locale en +set pattern #,##0.0###+;#- +begin +parse output breaks +// C sees this as -3426, don't understand why +3426 -3426 JK +3426+ 3426 +// J bails, but JDK will parse up to the space and get 34. +// C sees -34 +34 d1+ -34 JK +// JDK sees this as -1234 for some reason +// J bails b/c of trailing separators +// C parses until trailing separators, but sees -1234 +1,234,,,+ -1234 JK +1,234- -1234 +// J bails because of trailing separators +1,234,- -1234 J +// J bails here too +1234 - -1234 J + + + +test parse strict +set locale en +set pattern +#,##0.0###;(#) +set lenient 0 +begin +parse output breaks ++123d5 123 ++5347.25 5347.25 +// separators in wrong place cause failure, no separators ok. ++5,347.25 5347.25 +(5347.25) -5347.25 +(5,347.25) -5347.25 +// JDK does allow separators in the wrong place and parses as -5347.25 +(53,47.25) fail K +// strict requires prefix or suffix +5,347.25 fail ++3.52E4 35200 +(34.8E-3) -0.0348 +(3425E-1) -342.5 +// Strict doesn't allow separators in sci notation. +(3,425) -3425 +// JDK allows separators in sci notation and parses as -342.5 +(3,425E-1) fail K +// Both prefix and suffix needed for strict. +// JDK accepts this and parses as -342.5 +(3425E-1 fail K ++3.52EE4 3.52 ++1,234,567.8901 1234567.8901 +// With strict digit separators don't have to be the right type +// JDK doesn't acknowledge space as a separator ++1 234 567.8901 1234567.8901 K +// In general the grouping separators have to match their expected +// location exactly. The only exception is when string being parsed +// have no separators at all. ++1,234,567.8901 1234567.8901 +// JDK doesn't require separators to be in the right place ++1,23,4567.8901 fail K ++1234,567.8901 fail K ++1,234567.8901 fail K ++1234567.8901 1234567.8901 +// Comma after decimal means parse to a comma ++123,456.78,9 123456.78 +// A decimal after a decimal means bail +// JDK parses as 123456.78 ++123,456.78.9 fail K ++79 79 ++79 79 ++ 79 fail +// JDK parses as -1945 +(1,945d1) fail K + +test parse strange prefix +set locale en +set positivePrefix dd +set negativePrefix ddd +begin +parse output +dd4582 4582 +ddd4582 -4582 + +test parse strange suffix +set locale en +set positiveSuffix dd +set negativePrefix +set negativeSuffix ddd +begin +parse output +4582dd 4582 +4582ddd -4582 + +test really strange suffix +set locale en +set positiveSuffix 9K +set negativePrefix +set negativeSuffix 9N +begin +parse output breaks +// C consumes the '9' as a digit and assumes number is negative +// J and JDK bail +6549K 654 CJK +// C consumes the '9' as a digit and assumes number is negative +// J and JDK bail +6549N -654 CJK + +test really strange prefix +set locale en +set positivePrefix 82 +set negativePrefix 28 +begin +parse output +8245 45 +2845 -45 + +test select +set locale sr +begin +format pattern plural +Inf 0 other +-Inf 0 other +NaN 0 other +Inf 0.0 other +-Inf 0.0 other +NaN 0.0 other +1 0 one +1 0.0 other +2 0 few +2 0.0 other +2 0E0 other +5.1 0.0 one +5.09 0.0 one + diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DataDrivenNumberFormatTestSuite.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DataDrivenNumberFormatTestSuite.java new file mode 100644 index 00000000000..614efbcacf0 --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DataDrivenNumberFormatTestSuite.java @@ -0,0 +1,327 @@ +/* + ******************************************************************************* + * Copyright (C) 2015, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ +package com.ibm.icu.dev.test.format; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.dev.test.TestUtil; +import com.ibm.icu.impl.Utility; + +/** + * A collection of methods to run the data driven number format test suite. + */ +public class DataDrivenNumberFormatTestSuite extends TestFmwk { + + /** + * Base class for code under test. + */ + public static abstract class CodeUnderTest { + + /** + * Returns the ID of the code under test. This ID is used to identify + * tests that are known to fail for this particular code under test. + * This implementation returns null which means that by default all + * tests should work with this code under test. + * @return 'J' means ICU4J, 'K' means JDK + */ + public Character Id() { + return null; + } + + /** + * Runs a single formatting test. On success, returns null. + * On failure, returns the error. This implementation just returns null. + * Subclasses should override. + * @param tuple contains the parameters of the format test. + */ + public String format(NumberFormatTestTuple tuple) { + return null; + } + + /** + * Runs a single toPattern test. On success, returns null. + * On failure, returns the error. This implementation just returns null. + * Subclasses should override. + * @param tuple contains the parameters of the format test. + */ + public String toPattern(NumberFormatTestTuple tuple) { + return null; + } + + /** + * Runs a single parse test. On success, returns null. + * On failure, returns the error. This implementation just returns null. + * Subclasses should override. + * @param tuple contains the parameters of the format test. + */ + public String parse(NumberFormatTestTuple tuple) { + return null; + } + + /** + * Runs a single select test. On success, returns null. + * On failure, returns the error. This implementation just returns null. + * Subclasses should override. + * @param tuple contains the parameters of the format test. + */ + public String select(NumberFormatTestTuple tuple) { + return null; + } + } + + private static enum RunMode { + SKIP_KNOWN_FAILURES, + INCLUDE_KNOWN_FAILURES + } + + private final CodeUnderTest codeUnderTest; + private String fileLine = null; + private int fileLineNumber = 0; + private String fileTestName = ""; + private NumberFormatTestTuple tuple = new NumberFormatTestTuple(); + + /** + * Runs all the tests in the data driven test suite against codeUnderTest. + * @param params this.params from TestFmwk. + * @param fileName The name of the test file. A relative file name under + * com/ibm/icu/dev/data such as "data.txt" + * @param codeUnderTest the code under test + */ + static void runSuite( + TestParams params, String fileName, CodeUnderTest codeUnderTest) { + new DataDrivenNumberFormatTestSuite(params, codeUnderTest) + .run(fileName, RunMode.SKIP_KNOWN_FAILURES); + } + + /** + * Runs every format test in data driven test suite including those + * that are known to fail. + * + * @param params this.params from TestFmwk + * @param fileName The name of the test file. A relative file name under + * com/ibm/icu/dev/data such as "data.txt" + * @param codeUnderTest the code under test + */ + static void runFormatSuiteIncludingKnownFailures( + TestParams params, String fileName, CodeUnderTest codeUnderTest) { + new DataDrivenNumberFormatTestSuite(params, codeUnderTest) + .run(fileName, RunMode.INCLUDE_KNOWN_FAILURES); + } + + private DataDrivenNumberFormatTestSuite( + TestParams params, CodeUnderTest codeUnderTest) { + this.params = params; + this.codeUnderTest = codeUnderTest; + } + + private void run(String fileName, RunMode runMode) { + Character codeUnderTestIdObj = codeUnderTest.Id(); + char codeUnderTestId = + codeUnderTestIdObj == null ? 0 : Character.toUpperCase(codeUnderTestIdObj.charValue()); + BufferedReader in = null; + try { + in = TestUtil.getDataReader("numberformattestspecification.txt", "UTF-8"); + // read first line and remove BOM if present + readLine(in); + if (fileLine != null && fileLine.charAt(0) == '\uFEFF') { + fileLine = fileLine.substring(1); + } + + int state = 0; + List columnValues; + List columnNames = null; + while (true) { + if (fileLine == null || fileLine.length() == 0) { + if (!readLine(in)) { + break; + } + if (fileLine.isEmpty() && state == 2) { + state = 0; + } + continue; + } + if (fileLine.startsWith("//")) { + fileLine = null; + continue; + } + // Initial setup of test. + if (state == 0) { + if (fileLine.startsWith("test ")) { + fileTestName = fileLine; + tuple = new NumberFormatTestTuple(); + } else if (fileLine.startsWith("set ")) { + if (!setTupleField()) { + return; + } + } else if(fileLine.startsWith("begin")) { + state = 1; + } else { + showError("Unrecognized verb."); + return; + } + // column specification + } else if (state == 1) { + columnNames = splitBy((char) 0x09); + state = 2; + // run the tests + } else { + int columnNamesSize = columnNames.size(); + columnValues = splitBy(columnNamesSize, (char) 0x09); + int columnValuesSize = columnValues.size(); + for (int i = 0; i < columnValuesSize; ++i) { + if (!setField(columnNames.get(i), columnValues.get(i))) { + return; + } + } + for (int i = columnValuesSize; i < columnNamesSize; ++i) { + if (!clearField(columnNames.get(i))) { + return; + } + } + if (runMode == RunMode.INCLUDE_KNOWN_FAILURES + || !breaks(codeUnderTestId)) { + String errorMessage = isPass(tuple); + if (errorMessage != null) { + showError(errorMessage); + } + } + } + fileLine = null; + } + } catch (Exception e) { + showError(e.toString()); + e.printStackTrace(); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private boolean breaks(char code) { + String breaks = tuple.breaks == null ? "" : tuple.breaks; + return (breaks.toUpperCase().indexOf(code) != -1); + } + + private static boolean isSpace(char c) { + return (c == 0x09 || c == 0x20 || c == 0x3000); + } + + private boolean setTupleField() { + List parts = splitBy(3, (char) 0x20); + if (parts.size() < 3) { + showError("Set expects 2 parameters"); + return false; + } + return setField(parts.get(1), parts.get(2)); + } + + private boolean setField(String name, String value) { + try { + tuple.setField(name, Utility.unescape(value)); + return true; + } catch (Exception e) { + showError("No such field: " + name + ", or bad value: " + value); + return false; + } + } + + private boolean clearField(String name) { + try { + tuple.clearField(name); + return true; + } catch (Exception e) { + showError("Field cannot be clared: " + name); + return false; + } + } + + private void showError(String message) { + errln(String.format("line %d: %s\n%s\n%s", fileLineNumber, Utility.escape(message), fileTestName,fileLine)); + } + + private List splitBy(char delimiter) { + return splitBy(Integer.MAX_VALUE, delimiter); + } + + private List splitBy(int max, char delimiter) { + ArrayList result = new ArrayList(); + int colIdx = 0; + int colStart = 0; + int len = fileLine.length(); + for (int idx = 0; colIdx < max - 1 && idx < len; ++idx) { + char ch = fileLine.charAt(idx); + if (ch == delimiter) { + result.add( + fileLine.substring(colStart, idx)); + ++colIdx; + colStart = idx + 1; + } + } + result.add(fileLine.substring(colStart, len)); + return result; + } + + private boolean readLine(BufferedReader in) throws IOException { + String line = in.readLine(); + if (line == null) { + fileLine = null; + return false; + } + ++fileLineNumber; + // Strip trailing comments and spaces + int idx = line.length(); + for (; idx > 0; idx--) { + if (!isSpace(line.charAt(idx -1))) { + break; + } + } + fileLine = idx == 0 ? "" : line; + return true; + } + + private String isPass(NumberFormatTestTuple tuple) { + StringBuilder result = new StringBuilder(); + if (tuple.format != null && tuple.output != null) { + String errorMessage = codeUnderTest.format(tuple); + if (errorMessage != null) { + result.append(errorMessage); + } + } else if (tuple.toPattern != null || tuple.toLocalizedPattern != null) { + String errorMessage = codeUnderTest.toPattern(tuple); + if (errorMessage != null) { + result.append(errorMessage); + } + } else if (tuple.parse != null && tuple.output != null) { + String errorMessage = codeUnderTest.parse(tuple); + if (errorMessage != null) { + result.append(errorMessage); + } + } else if (tuple.plural != null) { + String errorMessage = codeUnderTest.select(tuple); + if (errorMessage != null) { + result.append(errorMessage); + } + } else { + result.append("Unrecognized test type."); + } + if (result.length() > 0) { + result.append(": "); + result.append(tuple); + return result.toString(); + } + return null; + } +} diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index aaf01d90531..3ad1419ef25 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -41,6 +41,354 @@ import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { + + private static ULocale EN = new ULocale("en"); + + private static Number toNumber(String s) { + if (s.equals("NaN")) { + return Double.NaN; + } else if (s.equals("-Inf")) { + return Double.NEGATIVE_INFINITY; + } else if (s.equals("Inf")) { + return Double.POSITIVE_INFINITY; + } + return new BigDecimal(s); + } + + + private DataDrivenNumberFormatTestSuite.CodeUnderTest ICU = + new DataDrivenNumberFormatTestSuite.CodeUnderTest() { + @Override + public Character Id() { return 'J'; } + + @Override + public String format(NumberFormatTestTuple tuple) { + DecimalFormat fmt = newDecimalFormat(tuple); + String actual = fmt.format(toNumber(tuple.format)); + String expected = tuple.output; + if (!expected.equals(actual)) { + return "Expected " + expected + ", got " + actual; + } + return null; + } + + @Override + public String toPattern(NumberFormatTestTuple tuple) { + DecimalFormat fmt = newDecimalFormat(tuple); + StringBuilder result = new StringBuilder(); + if (tuple.toPattern != null) { + String expected = tuple.toPattern; + String actual = fmt.toPattern(); + if (!expected.equals(actual)) { + result.append("Expected toPattern=" + expected + ", got " + actual); + } + } + if (tuple.toLocalizedPattern != null) { + String expected = tuple.toLocalizedPattern; + String actual = fmt.toLocalizedPattern(); + if (!expected.equals(actual)) { + result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); + } + } + return result.length() == 0 ? null : result.toString(); + } + + @Override + public String parse(NumberFormatTestTuple tuple) { + DecimalFormat fmt = newDecimalFormat(tuple); + int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue(); + fmt.setParseStrict(lenient == 0 ? true : false); + ParsePosition ppos = new ParsePosition(0); + Number actual = fmt.parse(tuple.parse, ppos); + if (ppos.getIndex() == 0) { + if (!tuple.output.equals("fail")) { + return "Parse error expected."; + } + return null; + } + if (tuple.output.equals("fail")) { + return "Parse succeeded, but was expected to fail."; + } + Number expected = toNumber(tuple.output); + // number types cannot be compared, this is the best we can do. + if (expected.doubleValue() != (actual.doubleValue())) { + return "Expected: " + expected + ", got: " + actual; + } + return null; + } + + /** + * @param tuple + * @return + */ + private DecimalFormat newDecimalFormat(NumberFormatTestTuple tuple) { + + DecimalFormat fmt = new DecimalFormat( + tuple.pattern == null ? "0" : tuple.pattern, + new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale)); + adjustDecimalFormat(tuple, fmt); + return fmt; + } + /** + * @param tuple + * @param fmt + */ + private void adjustDecimalFormat(NumberFormatTestTuple tuple, DecimalFormat fmt) { + if (tuple.minIntegerDigits != null) { + fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); + } + if (tuple.maxIntegerDigits != null) { + fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); + } + if (tuple.minFractionDigits != null) { + fmt.setMinimumFractionDigits(tuple.minFractionDigits); + } + if (tuple.maxFractionDigits != null) { + fmt.setMaximumFractionDigits(tuple.maxFractionDigits); + } + if (tuple.currency != null) { + fmt.setCurrency(tuple.currency); + } + if (tuple.minGroupingDigits != null) { + // Oops we don't support this. + } + if (tuple.useSigDigits != null) { + fmt.setSignificantDigitsUsed( + tuple.useSigDigits != 0); + } + if (tuple.minSigDigits != null) { + fmt.setMinimumSignificantDigits(tuple.minSigDigits); + } + if (tuple.maxSigDigits != null) { + fmt.setMaximumSignificantDigits(tuple.maxSigDigits); + } + if (tuple.useGrouping != null) { + fmt.setGroupingUsed(tuple.useGrouping != 0); + } + if (tuple.multiplier != null) { + fmt.setMultiplier(tuple.multiplier); + } + if (tuple.roundingIncrement != null) { + fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue()); + } + if (tuple.formatWidth != null) { + fmt.setFormatWidth(tuple.formatWidth); + } + if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { + fmt.setPadCharacter(tuple.padCharacter.charAt(0)); + } + if (tuple.useScientific != null) { + fmt.setScientificNotation(tuple.useScientific != 0); + } + if (tuple.grouping != null) { + fmt.setGroupingSize(tuple.grouping); + } + if (tuple.grouping2 != null) { + fmt.setSecondaryGroupingSize(tuple.grouping2); + } + if (tuple.roundingMode != null) { + fmt.setRoundingMode(tuple.roundingMode); + } + if (tuple.currencyUsage != null) { + fmt.setCurrencyUsage(tuple.currencyUsage); + } if (tuple.minimumExponentDigits != null) { + fmt.setMinimumExponentDigits( + tuple.minimumExponentDigits.byteValue()); + } + if (tuple.exponentSignAlwaysShown != null) { + fmt.setExponentSignAlwaysShown( + tuple.exponentSignAlwaysShown != 0); + } + if (tuple.decimalSeparatorAlwaysShown != null) { + fmt.setDecimalSeparatorAlwaysShown( + tuple.decimalSeparatorAlwaysShown != 0); + } + if (tuple.padPosition != null) { + fmt.setPadPosition(tuple.padPosition); + } + if (tuple.positivePrefix != null) { + fmt.setPositivePrefix(tuple.positivePrefix); + } + if (tuple.positiveSuffix != null) { + fmt.setPositiveSuffix(tuple.positiveSuffix); + } + if (tuple.negativePrefix != null) { + fmt.setNegativePrefix(tuple.negativePrefix); + } + if (tuple.negativeSuffix != null) { + fmt.setNegativeSuffix(tuple.negativeSuffix); + } + if (tuple.localizedPattern != null) { + fmt.applyLocalizedPattern(tuple.localizedPattern); + } + } + }; + + + private DataDrivenNumberFormatTestSuite.CodeUnderTest JDK = + new DataDrivenNumberFormatTestSuite.CodeUnderTest() { + @Override + public Character Id() { return 'K'; } + + @Override + public String format(NumberFormatTestTuple tuple) { + java.text.DecimalFormat fmt = newDecimalFormat(tuple); + String actual = fmt.format(toNumber(tuple.format)); + String expected = tuple.output; + if (!expected.equals(actual)) { + return "Expected " + expected + ", got " + actual; + } + return null; + } + + @Override + public String toPattern(NumberFormatTestTuple tuple) { + java.text.DecimalFormat fmt = newDecimalFormat(tuple); + StringBuilder result = new StringBuilder(); + if (tuple.toPattern != null) { + String expected = tuple.toPattern; + String actual = fmt.toPattern(); + if (!expected.equals(actual)) { + result.append("Expected toPattern=" + expected + ", got " + actual); + } + } + if (tuple.toLocalizedPattern != null) { + String expected = tuple.toLocalizedPattern; + String actual = fmt.toLocalizedPattern(); + if (!expected.equals(actual)) { + result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); + } + } + return result.length() == 0 ? null : result.toString(); + } + + @Override + public String parse(NumberFormatTestTuple tuple) { + java.text.DecimalFormat fmt = newDecimalFormat(tuple); + ParsePosition ppos = new ParsePosition(0); + Number actual = fmt.parse(tuple.parse, ppos); + if (ppos.getIndex() == 0) { + if (!tuple.output.equals("fail")) { + return "Parse error expected."; + } + return null; + } + if (tuple.output.equals("fail")) { + return "Parse succeeded: "+actual+", but was expected to fail."; + } + Number expected = toNumber(tuple.output); + // number types cannot be compared, this is the best we can do. + if (expected.doubleValue() != actual.doubleValue()) { + return "Expected: " + expected + ", got: " + actual; + } + return null; + } + + /** + * @param tuple + * @return + */ + private java.text.DecimalFormat newDecimalFormat(NumberFormatTestTuple tuple) { + java.text.DecimalFormat fmt = new java.text.DecimalFormat( + tuple.pattern == null ? "0" : tuple.pattern, + new java.text.DecimalFormatSymbols( + (tuple.locale == null ? EN : tuple.locale).toLocale())); + adjustDecimalFormat(tuple, fmt); + return fmt; + } + + /** + * @param tuple + * @param fmt + */ + private void adjustDecimalFormat(NumberFormatTestTuple tuple, java.text.DecimalFormat fmt) { + if (tuple.minIntegerDigits != null) { + fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); + } + if (tuple.maxIntegerDigits != null) { + fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); + } + if (tuple.minFractionDigits != null) { + fmt.setMinimumFractionDigits(tuple.minFractionDigits); + } + if (tuple.maxFractionDigits != null) { + fmt.setMaximumFractionDigits(tuple.maxFractionDigits); + } + if (tuple.currency != null) { + fmt.setCurrency(java.util.Currency.getInstance(tuple.currency.toString())); + } + if (tuple.minGroupingDigits != null) { + // Oops we don't support this. + } + if (tuple.useSigDigits != null) { + // Oops we don't support this + } + if (tuple.minSigDigits != null) { + // Oops we don't support this + } + if (tuple.maxSigDigits != null) { + // Oops we don't support this + } + if (tuple.useGrouping != null) { + fmt.setGroupingUsed(tuple.useGrouping != 0); + } + if (tuple.multiplier != null) { + fmt.setMultiplier(tuple.multiplier); + } + if (tuple.roundingIncrement != null) { + // Not supported + } + if (tuple.formatWidth != null) { + // Not supported + } + if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { + // Not supported + } + if (tuple.useScientific != null) { + // Not supported + } + if (tuple.grouping != null) { + fmt.setGroupingSize(tuple.grouping); + } + if (tuple.grouping2 != null) { + // Not supported + } + if (tuple.roundingMode != null) { + // Not supported + } + if (tuple.currencyUsage != null) { + // Not supported + } + if (tuple.minimumExponentDigits != null) { + // Not supported + } + if (tuple.exponentSignAlwaysShown != null) { + // Not supported + } + if (tuple.decimalSeparatorAlwaysShown != null) { + fmt.setDecimalSeparatorAlwaysShown( + tuple.decimalSeparatorAlwaysShown != 0); + } + if (tuple.padPosition != null) { + // Not supported + } + if (tuple.positivePrefix != null) { + fmt.setPositivePrefix(tuple.positivePrefix); + } + if (tuple.positiveSuffix != null) { + fmt.setPositiveSuffix(tuple.positiveSuffix); + } + if (tuple.negativePrefix != null) { + fmt.setNegativePrefix(tuple.negativePrefix); + } + if (tuple.negativeSuffix != null) { + fmt.setNegativeSuffix(tuple.negativeSuffix); + } + if (tuple.localizedPattern != null) { + fmt.applyLocalizedPattern(tuple.localizedPattern); + } + } + }; public static void main(String[] args) throws Exception { new NumberFormatTest().run(args); @@ -3727,6 +4075,17 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } + + public void TestDataDrivenICU() { + DataDrivenNumberFormatTestSuite.runSuite( + this.params, "numberformattestspecification.txt", ICU); + } + + public void TestDataDrivenJDK() { + DataDrivenNumberFormatTestSuite.runSuite( + this.params, "numberformattestspecification.txt", JDK); + } + public void TestCurrFmtNegSameAsPositive() { DecimalFormatSymbols decfmtsym = DecimalFormatSymbols.getInstance(Locale.US); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTestTuple.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTestTuple.java new file mode 100644 index 00000000000..54f97268e4d --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTestTuple.java @@ -0,0 +1,501 @@ +/* + ******************************************************************************* + * Copyright (C) 2015, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ +package com.ibm.icu.dev.test.format; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import com.ibm.icu.math.BigDecimal; +import com.ibm.icu.text.DecimalFormat; +import com.ibm.icu.text.NumberFormat; +import com.ibm.icu.util.Currency; +import com.ibm.icu.util.ULocale; + +/** + * A representation of a single NumberFormat specification test from a data driven test file. + *

+ * The purpose of this class is to hide the details of the data driven test file from the + * main testing code. + *

+ * This class contains fields describing an attribute of the test that may or may + * not be set. The name of each attribute corresponds to the name used in the + * data driven test file. + *

+ * Adding new attributes + *

+ * Each attribute name is lower case. Moreover, for each attribute there is also a + * setXXX method for that attribute that is used to initialize the attribute from a + * String value read from the data file. For example, there is a setLocale(String) method + * for the locale attribute and a setCurrency(String) method for the currency attribute. + * In general, for an attribute named abcd, the setter will be setAbcd(String). + * This naming rule must be strictly followed or else the test runner will not know how to + * initialize instances of this class. + *

+ * In addition each attribute is listed in the fieldOrdering static array which specifies + * The order that attributes are printed whenever there is a test failure. + *

+ * To add a new attribute, first create a public field for it. + * Next, add the attribute name to the fieldOrdering array. + * Finally, create a setter method for it. + * + * @author rocketman + */ +public class NumberFormatTestTuple { + + /** + * The locale. + */ + public ULocale locale = null; + + /** + * The currency. + */ + public Currency currency = null; + + /** + * The pattern to initialize the formatter, for example 0.00" + */ + public String pattern = null; + + /** + * The value to format as a string. For example 1234.5 would be "1234.5" + */ + public String format = null; + + /** + * The formatted value. + */ + public String output = null; + + /** + * Field for arbitrary comments. + */ + public String comment = null; + + public Integer minIntegerDigits = null; + public Integer maxIntegerDigits = null; + public Integer minFractionDigits = null; + public Integer maxFractionDigits = null; + public Integer minGroupingDigits = null; + public Integer useSigDigits = null; + public Integer minSigDigits = null; + public Integer maxSigDigits = null; + public Integer useGrouping = null; + public Integer multiplier = null; + public Double roundingIncrement = null; + public Integer formatWidth = null; + public String padCharacter = null; + public Integer useScientific = null; + public Integer grouping = null; + public Integer grouping2 = null; + public Integer roundingMode = null; + public Currency.CurrencyUsage currencyUsage = null; + public Integer minimumExponentDigits = null; + public Integer exponentSignAlwaysShown = null; + public Integer decimalSeparatorAlwaysShown = null; + public Integer padPosition = null; + public String positivePrefix = null; + public String positiveSuffix = null; + public String negativePrefix = null; + public String negativeSuffix = null; + public String localizedPattern = null; + public String toPattern = null; + public String toLocalizedPattern = null; + public Integer style = null; + public String parse = null; + public Integer lenient = null; + public String plural = null; + + + + /** + * nothing or empty means that test ought to work for both C and JAVA; + * "C" means test is known to fail in C. "J" means test is known to fail in JAVA. + * "CJ" means test is known to fail for both languages. + */ + public String breaks = null; + + private static Map roundingModeMap = + new HashMap(); + + static { + roundingModeMap.put("ceiling", BigDecimal.ROUND_CEILING); + roundingModeMap.put("floor", BigDecimal.ROUND_FLOOR); + roundingModeMap.put("down", BigDecimal.ROUND_DOWN); + roundingModeMap.put("up", BigDecimal.ROUND_UP); + roundingModeMap.put("halfEven", BigDecimal.ROUND_HALF_EVEN); + roundingModeMap.put("halfDown", BigDecimal.ROUND_HALF_DOWN); + roundingModeMap.put("halfUp", BigDecimal.ROUND_HALF_UP); + roundingModeMap.put("unnecessary", BigDecimal.ROUND_UNNECESSARY); + } + + private static Map currencyUsageMap = + new HashMap(); + + static { + currencyUsageMap.put("standard", Currency.CurrencyUsage.STANDARD); + currencyUsageMap.put("cash", Currency.CurrencyUsage.CASH); + } + + private static Map padPositionMap = + new HashMap(); + + static { + // TODO: Fix so that it doesn't depend on DecimalFormat. + padPositionMap.put("beforePrefix", DecimalFormat.PAD_BEFORE_PREFIX); + padPositionMap.put("afterPrefix", DecimalFormat.PAD_AFTER_PREFIX); + padPositionMap.put("beforeSuffix", DecimalFormat.PAD_BEFORE_SUFFIX); + padPositionMap.put("afterSuffix", DecimalFormat.PAD_AFTER_SUFFIX); + } + + private static Map formatStyleMap = + new HashMap(); + + static { + formatStyleMap.put("decimal", NumberFormat.NUMBERSTYLE); + formatStyleMap.put("currency", NumberFormat.CURRENCYSTYLE); + formatStyleMap.put("percent", NumberFormat.PERCENTSTYLE); + formatStyleMap.put("scientific", NumberFormat.SCIENTIFICSTYLE); + formatStyleMap.put("currencyIso", NumberFormat.ISOCURRENCYSTYLE); + formatStyleMap.put("currencyPlural", NumberFormat.PLURALCURRENCYSTYLE); + formatStyleMap.put("currencyAccounting", NumberFormat.ACCOUNTINGCURRENCYSTYLE); + formatStyleMap.put("cashCurrency", NumberFormat.CASHCURRENCYSTYLE); + } + + // Add any new fields here. On test failures, fields are printed in the same order they + // appear here. + private static String[] fieldOrdering = { + "locale", + "currency", + "pattern", + "format", + "output", + "comment", + "minIntegerDigits", + "maxIntegerDigits", + "minFractionDigits", + "maxFractionDigits", + "minGroupingDigits", + "breaks", + "useSigDigits", + "minSigDigits", + "maxSigDigits", + "useGrouping", + "multiplier", + "roundingIncrement", + "formatWidth", + "padCharacter", + "useScientific", + "grouping", + "grouping2", + "roundingMode", + "currencyUsage", + "minimumExponentDigits", + "exponentSignAlwaysShown", + "decimalSeparatorAlwaysShown", + "padPosition", + "positivePrefix", + "positiveSuffix", + "negativePrefix", + "negativeSuffix", + "localizedPattern", + "toPattern", + "toLocalizedPattern", + "style", + "parse", + "lenient", + "plural", + }; + + static { + HashSet set = new HashSet(); + for (String s : fieldOrdering) { + if (!set.add(s)) { + throw new ExceptionInInitializerError(s + "is a duplicate field."); + } + } + } + + private static T fromString(Map map, String key) { + T value = map.get(key); + if (value == null) { + throw new IllegalArgumentException("Bad value: "+ key); + } + return value; + } + + // start field setters. + // add setter for each new field in this block. + + public void setLocale(String value) { + locale = new ULocale(value); + } + + public void setCurrency(String value) { + currency = Currency.getInstance(value); + } + + public void setPattern(String value) { + pattern = value; + } + + public void setFormat(String value) { + format = value; + } + + public void setOutput(String value) { + output = value; + } + + public void setComment(String value) { + comment = value; + } + + public void setMinIntegerDigits(String value) { + minIntegerDigits = Integer.valueOf(value); + } + + public void setMaxIntegerDigits(String value) { + maxIntegerDigits = Integer.valueOf(value); + } + + public void setMinFractionDigits(String value) { + minFractionDigits = Integer.valueOf(value); + } + + public void setMaxFractionDigits(String value) { + maxFractionDigits = Integer.valueOf(value); + } + + public void setMinGroupingDigits(String value) { + minGroupingDigits = Integer.valueOf(value); + } + + public void setBreaks(String value) { + breaks = value; + } + + public void setUseSigDigits(String value) { + useSigDigits = Integer.valueOf(value); + } + + public void setMinSigDigits(String value) { + minSigDigits = Integer.valueOf(value); + } + + public void setMaxSigDigits(String value) { + maxSigDigits = Integer.valueOf(value); + } + + public void setUseGrouping(String value) { + useGrouping = Integer.valueOf(value); + } + + public void setMultiplier(String value) { + multiplier = Integer.valueOf(value); + } + + public void setRoundingIncrement(String value) { + roundingIncrement = Double.valueOf(value); + } + + public void setFormatWidth(String value) { + formatWidth = Integer.valueOf(value); + } + + public void setPadCharacter(String value) { + padCharacter = value; + } + + public void setUseScientific(String value) { + useScientific = Integer.valueOf(value); + } + + public void setGrouping(String value) { + grouping = Integer.valueOf(value); + } + + public void setGrouping2(String value) { + grouping2 = Integer.valueOf(value); + } + + public void setRoundingMode(String value) { + roundingMode = fromString(roundingModeMap, value); + } + + public void setCurrencyUsage(String value) { + currencyUsage = fromString(currencyUsageMap, value); + } + + public void setMinimumExponentDigits(String value) { + minimumExponentDigits = Integer.valueOf(value); + } + + public void setExponentSignAlwaysShown(String value) { + exponentSignAlwaysShown = Integer.valueOf(value); + } + + public void setDecimalSeparatorAlwaysShown(String value) { + decimalSeparatorAlwaysShown = Integer.valueOf(value); + } + + public void setPadPosition(String value) { + padPosition = fromString(padPositionMap, value); + } + + public void setPositivePrefix(String value) { + positivePrefix = value; + } + + public void setPositiveSuffix(String value) { + positiveSuffix = value; + } + + public void setNegativePrefix(String value) { + negativePrefix = value; + } + + public void setNegativeSuffix(String value) { + negativeSuffix = value; + } + + public void setLocalizedPattern(String value) { + localizedPattern = value; + } + + public void setToPattern(String value) { + toPattern = value; + } + + public void setToLocalizedPattern(String value) { + toLocalizedPattern = value; + } + + public void setStyle(String value) { + style = fromString(formatStyleMap, value); + } + + public void setParse(String value) { + parse = value; + } + + public void setLenient(String value) { + lenient = Integer.valueOf(value); + } + + public void setPlural(String value) { + plural = value; + } + + // end field setters. + + // start of field clearers + // Add clear methods that can be set in one test and cleared + // in the next i.e the breaks field. + + public void clearBreaks() { + breaks = null; + } + + public void clearUseGrouping() { + useGrouping = null; + } + + public void clearGrouping2() { + grouping2 = null; + } + + public void clearGrouping() { + grouping = null; + } + + public void clearMinGroupingDigits() { + minGroupingDigits = null; + } + + public void clearUseScientific() { + useScientific = null; + } + + public void clearDecimalSeparatorAlwaysShown() { + decimalSeparatorAlwaysShown = null; + } + + // end field clearers + + public void setField(String fieldName, String valueString) + throws NoSuchMethodException { + Method m = getClass().getMethod( + fieldToSetter(fieldName), String.class); + try { + m.invoke(this, valueString); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public void clearField(String fieldName) + throws NoSuchMethodException { + Method m = getClass().getMethod(fieldToClearer(fieldName)); + try { + m.invoke(this); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("{"); + boolean first = true; + for (String fieldName : fieldOrdering) { + try { + Field field = getClass().getField(fieldName); + Object optionalValue = field.get(this); + if (optionalValue == null) { + continue; + } + if (!first) { + result.append(", "); + } + first = false; + result.append(fieldName); + result.append(": "); + result.append(optionalValue); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (SecurityException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + result.append("}"); + return result.toString(); + } + + private static String fieldToSetter(String fieldName) { + return "set" + + Character.toUpperCase(fieldName.charAt(0)) + + fieldName.substring(1); + } + + private static String fieldToClearer(String fieldName) { + return "clear" + + Character.toUpperCase(fieldName.charAt(0)) + + fieldName.substring(1); + } + +} -- 2.40.0