1 // 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 package com.ibm.icu.dev.test.format; 4 5 import java.math.BigDecimal; 6 import java.math.RoundingMode; 7 import java.text.ParsePosition; 8 9 import org.junit.Test; 10 11 import com.ibm.icu.dev.test.TestUtil; 12 import com.ibm.icu.impl.number.DecimalFormatProperties; 13 import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode; 14 import com.ibm.icu.impl.number.Padder.PadPosition; 15 import com.ibm.icu.impl.number.PatternStringParser; 16 import com.ibm.icu.impl.number.PatternStringUtils; 17 import com.ibm.icu.impl.number.parse.NumberParserImpl; 18 import com.ibm.icu.number.LocalizedNumberFormatter; 19 import com.ibm.icu.number.NumberFormatter; 20 import com.ibm.icu.text.DecimalFormat; 21 import com.ibm.icu.text.DecimalFormat.PropertySetter; 22 import com.ibm.icu.text.DecimalFormatSymbols; 23 import com.ibm.icu.util.CurrencyAmount; 24 import com.ibm.icu.util.ULocale; 25 26 public class NumberFormatDataDrivenTest { 27 28 private static ULocale EN = new ULocale("en"); 29 30 private static Number toNumber(String s) { 31 if (s.equals("NaN")) { 32 return Double.NaN; 33 } else if (s.equals("-Inf")) { 34 return Double.NEGATIVE_INFINITY; 35 } else if (s.equals("Inf")) { 36 return Double.POSITIVE_INFINITY; 37 } 38 return new BigDecimal(s); 39 } 40 41 /** 42 * Standard function for comparing expected and actual parse results. Handles NaN, Infinity, and 43 * failure cases. 44 */ 45 private static String compareParseResult(String expected, Number actual, ParsePosition ppos) { 46 if (actual == null && ppos.getIndex() != 0) { 47 throw new AssertionError("Error: value is null but parse position is not zero"); 48 } 49 if (ppos.getIndex() == 0) { 50 return "Parse failed; got " + actual + ", but expected " + expected; 51 } 52 if (expected.equals("NaN")) { 53 if (!Double.isNaN(actual.doubleValue())) { 54 return "Expected NaN, but got: " + actual; 55 } 56 return null; 57 } else if (expected.equals("Inf")) { 58 if (!Double.isInfinite(actual.doubleValue()) 59 || Double.compare(actual.doubleValue(), 0.0) < 0) { 60 return "Expected Inf, but got: " + actual; 61 } 62 return null; 63 } else if (expected.equals("-Inf")) { 64 if (!Double.isInfinite(actual.doubleValue()) 65 || Double.compare(actual.doubleValue(), 0.0) > 0) { 66 return "Expected -Inf, but got: " + actual; 67 } 68 return null; 69 } else if (expected.equals("fail")) { 70 return null; 71 } else if (actual.toString().equals("Infinity")) { 72 return "Expected " + expected + ", but got Infinity"; 73 } else { 74 BigDecimal expectedDecimal = new BigDecimal(expected); 75 BigDecimal actualDecimal; 76 try { 77 actualDecimal = new BigDecimal(actual.toString()); 78 } catch (NumberFormatException e) { 79 throw new AssertionError("Could not convert to BigDecimal: " + actual.toString() + " - " + e.getMessage()); 80 } 81 if (expectedDecimal.compareTo(actualDecimal) != 0) { 82 return "Expected: " + expected + ", got: " + actual; 83 } else { 84 return null; 85 } 86 } 87 } 88 89 /** 90 * Standard function for comparing expected and actual parse-currency results. Handles failure cases. 91 * Does not currently handle NaN or Infinity because there are no parse-currency cases with NaN or 92 * Infinity. 93 */ 94 private static String compareParseCurrencyResult( 95 String expected, 96 String expectedCurrency, 97 CurrencyAmount actual, 98 ParsePosition ppos) { 99 if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) { 100 return "Parse failed; got " + actual + ", but expected " + expected; 101 } 102 if (expected.equals("fail")) { 103 return null; 104 } 105 BigDecimal expectedNumber = new BigDecimal(expected); 106 if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) { 107 return "Wrong number: Expected: " + expectedNumber + ", got: " + actual; 108 } 109 if (!expectedCurrency.equals(actual.getCurrency().toString())) { 110 return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual; 111 } 112 return null; 113 } 114 115 /** 116 * Main ICU4J DecimalFormat data-driven test. 117 */ 118 private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU4J = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 119 @Override 120 public Character Id() { 121 return 'J'; 122 } 123 124 @Override 125 public String format(DataDrivenNumberFormatTestData tuple) { 126 DecimalFormat fmt = createDecimalFormat(tuple); 127 String actual = fmt.format(toNumber(tuple.format)); 128 String expected = tuple.output; 129 if (!expected.equals(actual)) { 130 return "Expected " + expected + ", got " + actual; 131 } 132 return null; 133 } 134 135 @Override 136 public String toPattern(DataDrivenNumberFormatTestData tuple) { 137 DecimalFormat fmt = createDecimalFormat(tuple); 138 StringBuilder result = new StringBuilder(); 139 if (tuple.toPattern != null) { 140 String expected = tuple.toPattern; 141 String actual = fmt.toPattern(); 142 if (!expected.equals(actual)) { 143 result.append("Expected toPattern=" + expected + ", got " + actual); 144 } 145 } 146 if (tuple.toLocalizedPattern != null) { 147 String expected = tuple.toLocalizedPattern; 148 String actual = fmt.toLocalizedPattern(); 149 if (!expected.equals(actual)) { 150 result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); 151 } 152 } 153 return result.length() == 0 ? null : result.toString(); 154 } 155 156 @Override 157 public String parse(DataDrivenNumberFormatTestData tuple) { 158 DecimalFormat fmt = createDecimalFormat(tuple); 159 ParsePosition ppos = new ParsePosition(0); 160 Number actual = fmt.parse(tuple.parse, ppos); 161 return compareParseResult(tuple.output, actual, ppos); 162 } 163 164 @Override 165 public String parseCurrency(DataDrivenNumberFormatTestData tuple) { 166 DecimalFormat fmt = createDecimalFormat(tuple); 167 ParsePosition ppos = new ParsePosition(0); 168 CurrencyAmount actual = fmt.parseCurrency(tuple.parse, ppos); 169 return compareParseCurrencyResult(tuple.output, tuple.outputCurrency, actual, ppos); 170 } 171 172 /** 173 * @param tuple 174 * @return 175 */ 176 private DecimalFormat createDecimalFormat(DataDrivenNumberFormatTestData tuple) { 177 DecimalFormat fmt = new DecimalFormat(tuple.pattern == null ? "0" : tuple.pattern, 178 new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale)); 179 adjustDecimalFormat(tuple, fmt); 180 return fmt; 181 } 182 183 /** 184 * @param tuple 185 * @param fmt 186 */ 187 private void adjustDecimalFormat(DataDrivenNumberFormatTestData tuple, DecimalFormat fmt) { 188 if (tuple.minIntegerDigits != null) { 189 fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); 190 } 191 if (tuple.maxIntegerDigits != null) { 192 fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); 193 } 194 if (tuple.minFractionDigits != null) { 195 fmt.setMinimumFractionDigits(tuple.minFractionDigits); 196 } 197 if (tuple.maxFractionDigits != null) { 198 fmt.setMaximumFractionDigits(tuple.maxFractionDigits); 199 } 200 if (tuple.currency != null) { 201 fmt.setCurrency(tuple.currency); 202 } 203 if (tuple.minGroupingDigits != null) { 204 fmt.setMinimumGroupingDigits(tuple.minGroupingDigits); 205 } 206 if (tuple.useSigDigits != null) { 207 fmt.setSignificantDigitsUsed(tuple.useSigDigits != 0); 208 } 209 if (tuple.minSigDigits != null) { 210 fmt.setMinimumSignificantDigits(tuple.minSigDigits); 211 } 212 if (tuple.maxSigDigits != null) { 213 fmt.setMaximumSignificantDigits(tuple.maxSigDigits); 214 } 215 if (tuple.useGrouping != null) { 216 fmt.setGroupingUsed(tuple.useGrouping != 0); 217 } 218 if (tuple.multiplier != null) { 219 fmt.setMultiplier(tuple.multiplier); 220 } 221 if (tuple.roundingIncrement != null) { 222 fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue()); 223 } 224 if (tuple.formatWidth != null) { 225 fmt.setFormatWidth(tuple.formatWidth); 226 } 227 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 228 fmt.setPadCharacter(tuple.padCharacter.charAt(0)); 229 } 230 if (tuple.useScientific != null) { 231 fmt.setScientificNotation(tuple.useScientific != 0); 232 } 233 if (tuple.grouping != null) { 234 fmt.setGroupingSize(tuple.grouping); 235 } 236 if (tuple.grouping2 != null) { 237 fmt.setSecondaryGroupingSize(tuple.grouping2); 238 } 239 if (tuple.roundingMode != null) { 240 fmt.setRoundingMode(tuple.roundingMode); 241 } 242 if (tuple.currencyUsage != null) { 243 fmt.setCurrencyUsage(tuple.currencyUsage); 244 } 245 if (tuple.minimumExponentDigits != null) { 246 fmt.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue()); 247 } 248 if (tuple.exponentSignAlwaysShown != null) { 249 fmt.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0); 250 } 251 if (tuple.decimalSeparatorAlwaysShown != null) { 252 fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 253 } 254 if (tuple.padPosition != null) { 255 fmt.setPadPosition(tuple.padPosition); 256 } 257 if (tuple.positivePrefix != null) { 258 fmt.setPositivePrefix(tuple.positivePrefix); 259 } 260 if (tuple.positiveSuffix != null) { 261 fmt.setPositiveSuffix(tuple.positiveSuffix); 262 } 263 if (tuple.negativePrefix != null) { 264 fmt.setNegativePrefix(tuple.negativePrefix); 265 } 266 if (tuple.negativeSuffix != null) { 267 fmt.setNegativeSuffix(tuple.negativeSuffix); 268 } 269 if (tuple.signAlwaysShown != null) { 270 fmt.setSignAlwaysShown(tuple.signAlwaysShown != 0); 271 } 272 if (tuple.localizedPattern != null) { 273 fmt.applyLocalizedPattern(tuple.localizedPattern); 274 } 275 int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue(); 276 fmt.setParseStrict(lenient == 0); 277 if (tuple.parseIntegerOnly != null) { 278 fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 279 } 280 if (tuple.parseCaseSensitive != null) { 281 fmt.setParseCaseSensitive(tuple.parseCaseSensitive != 0); 282 } 283 if (tuple.decimalPatternMatchRequired != null) { 284 fmt.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0); 285 } 286 if (tuple.parseNoExponent != null) { 287 fmt.setParseNoExponent(tuple.parseNoExponent != 0); 288 } 289 } 290 }; 291 292 /** 293 * Backwards-compatibility test: snapshot of DecimalFormat from ICU 58. 294 */ 295 // Android patch: Android can't access DecimalFormat_ICU58 for testing (b/33448125). 296 // That class lived in a package under test and relied on package access, but 297 // 1.) Android Compatibility Test Suite (CTS) run tests with a different ClassLoader, 298 // preventing package access, and 299 // 2.) By default, the OpenJDK 9 toolchain won't compile non-libcore code that in 300 // libcore packages (see http://b/68224249). 301 /* 302 private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU58 = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 303 @Override 304 public Character Id() { 305 return 'H'; 306 } 307 308 @Override 309 public String format(DataDrivenNumberFormatTestData tuple) { 310 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 311 String actual = fmt.format(toNumber(tuple.format)); 312 String expected = tuple.output; 313 if (!expected.equals(actual)) { 314 return "Expected " + expected + ", got " + actual; 315 } 316 return null; 317 } 318 319 @Override 320 public String toPattern(DataDrivenNumberFormatTestData tuple) { 321 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 322 StringBuilder result = new StringBuilder(); 323 if (tuple.toPattern != null) { 324 String expected = tuple.toPattern; 325 String actual = fmt.toPattern(); 326 if (!expected.equals(actual)) { 327 result.append("Expected toPattern=" + expected + ", got " + actual); 328 } 329 } 330 if (tuple.toLocalizedPattern != null) { 331 String expected = tuple.toLocalizedPattern; 332 String actual = fmt.toLocalizedPattern(); 333 if (!expected.equals(actual)) { 334 result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); 335 } 336 } 337 return result.length() == 0 ? null : result.toString(); 338 } 339 340 @Override 341 public String parse(DataDrivenNumberFormatTestData tuple) { 342 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 343 ParsePosition ppos = new ParsePosition(0); 344 Number actual = fmt.parse(tuple.parse, ppos); 345 return compareParseResult(tuple.output, actual, ppos); 346 } 347 348 @Override 349 public String parseCurrency(DataDrivenNumberFormatTestData tuple) { 350 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 351 ParsePosition ppos = new ParsePosition(0); 352 CurrencyAmount actual = fmt.parseCurrency(tuple.parse, ppos); 353 return compareParseCurrencyResult(tuple.output, tuple.outputCurrency, actual, ppos); 354 } 355 356 /** 357 * @param tuple 358 * @return 359 * 360 private DecimalFormat_ICU58 createDecimalFormat(DataDrivenNumberFormatTestData tuple) { 361 362 DecimalFormat_ICU58 fmt = new DecimalFormat_ICU58( 363 tuple.pattern == null ? "0" : tuple.pattern, 364 new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale)); 365 adjustDecimalFormat(tuple, fmt); 366 return fmt; 367 } 368 369 /** 370 * @param tuple 371 * @param fmt 372 * 373 private void adjustDecimalFormat(DataDrivenNumberFormatTestData tuple, DecimalFormat_ICU58 fmt) { 374 if (tuple.minIntegerDigits != null) { 375 fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); 376 } 377 if (tuple.maxIntegerDigits != null) { 378 fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); 379 } 380 if (tuple.minFractionDigits != null) { 381 fmt.setMinimumFractionDigits(tuple.minFractionDigits); 382 } 383 if (tuple.maxFractionDigits != null) { 384 fmt.setMaximumFractionDigits(tuple.maxFractionDigits); 385 } 386 if (tuple.currency != null) { 387 fmt.setCurrency(tuple.currency); 388 } 389 if (tuple.minGroupingDigits != null) { 390 // Oops we don't support this. 391 } 392 if (tuple.useSigDigits != null) { 393 fmt.setSignificantDigitsUsed(tuple.useSigDigits != 0); 394 } 395 if (tuple.minSigDigits != null) { 396 fmt.setMinimumSignificantDigits(tuple.minSigDigits); 397 } 398 if (tuple.maxSigDigits != null) { 399 fmt.setMaximumSignificantDigits(tuple.maxSigDigits); 400 } 401 if (tuple.useGrouping != null) { 402 fmt.setGroupingUsed(tuple.useGrouping != 0); 403 } 404 if (tuple.multiplier != null) { 405 fmt.setMultiplier(tuple.multiplier); 406 } 407 if (tuple.roundingIncrement != null) { 408 fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue()); 409 } 410 if (tuple.formatWidth != null) { 411 fmt.setFormatWidth(tuple.formatWidth); 412 } 413 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 414 fmt.setPadCharacter(tuple.padCharacter.charAt(0)); 415 } 416 if (tuple.useScientific != null) { 417 fmt.setScientificNotation(tuple.useScientific != 0); 418 } 419 if (tuple.grouping != null) { 420 fmt.setGroupingSize(tuple.grouping); 421 } 422 if (tuple.grouping2 != null) { 423 fmt.setSecondaryGroupingSize(tuple.grouping2); 424 } 425 if (tuple.roundingMode != null) { 426 fmt.setRoundingMode(tuple.roundingMode); 427 } 428 if (tuple.currencyUsage != null) { 429 fmt.setCurrencyUsage(tuple.currencyUsage); 430 } 431 if (tuple.minimumExponentDigits != null) { 432 fmt.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue()); 433 } 434 if (tuple.exponentSignAlwaysShown != null) { 435 fmt.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0); 436 } 437 if (tuple.decimalSeparatorAlwaysShown != null) { 438 fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 439 } 440 if (tuple.padPosition != null) { 441 fmt.setPadPosition(tuple.padPosition); 442 } 443 if (tuple.positivePrefix != null) { 444 fmt.setPositivePrefix(tuple.positivePrefix); 445 } 446 if (tuple.positiveSuffix != null) { 447 fmt.setPositiveSuffix(tuple.positiveSuffix); 448 } 449 if (tuple.negativePrefix != null) { 450 fmt.setNegativePrefix(tuple.negativePrefix); 451 } 452 if (tuple.negativeSuffix != null) { 453 fmt.setNegativeSuffix(tuple.negativeSuffix); 454 } 455 if (tuple.signAlwaysShown != null) { 456 // Not supported. 457 } 458 if (tuple.localizedPattern != null) { 459 fmt.applyLocalizedPattern(tuple.localizedPattern); 460 } 461 int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue(); 462 fmt.setParseStrict(lenient == 0); 463 if (tuple.parseIntegerOnly != null) { 464 fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 465 } 466 if (tuple.parseCaseSensitive != null) { 467 // Not supported. 468 } 469 if (tuple.decimalPatternMatchRequired != null) { 470 fmt.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0); 471 } 472 if (tuple.parseNoExponent != null) { 473 // Oops, not supported for now 474 } 475 } 476 }; 477 */ 478 // Android patch end. 479 480 /** 481 * Test of available JDK APIs. 482 */ 483 private DataDrivenNumberFormatTestUtility.CodeUnderTest JDK = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 484 @Override 485 public Character Id() { 486 return 'K'; 487 } 488 489 @Override 490 public String format(DataDrivenNumberFormatTestData tuple) { 491 java.text.DecimalFormat fmt = createDecimalFormat(tuple); 492 String actual = fmt.format(toNumber(tuple.format)); 493 String expected = tuple.output; 494 if (!expected.equals(actual)) { 495 return "Expected " + expected + ", got " + actual; 496 } 497 return null; 498 } 499 500 @Override 501 public String toPattern(DataDrivenNumberFormatTestData tuple) { 502 java.text.DecimalFormat fmt = createDecimalFormat(tuple); 503 StringBuilder result = new StringBuilder(); 504 if (tuple.toPattern != null) { 505 String expected = tuple.toPattern; 506 String actual = fmt.toPattern(); 507 if (!expected.equals(actual)) { 508 result.append("Expected toPattern=" + expected + ", got " + actual); 509 } 510 } 511 if (tuple.toLocalizedPattern != null) { 512 String expected = tuple.toLocalizedPattern; 513 String actual = fmt.toLocalizedPattern(); 514 if (!expected.equals(actual)) { 515 result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); 516 } 517 } 518 return result.length() == 0 ? null : result.toString(); 519 } 520 521 @Override 522 public String parse(DataDrivenNumberFormatTestData tuple) { 523 java.text.DecimalFormat fmt = createDecimalFormat(tuple); 524 ParsePosition ppos = new ParsePosition(0); 525 Number actual = fmt.parse(tuple.parse, ppos); 526 return compareParseResult(tuple.output, actual, ppos); 527 } 528 529 /** 530 * @param tuple 531 * @return 532 */ 533 private java.text.DecimalFormat createDecimalFormat(DataDrivenNumberFormatTestData tuple) { 534 java.text.DecimalFormat fmt = new java.text.DecimalFormat( 535 tuple.pattern == null ? "0" : tuple.pattern, 536 new java.text.DecimalFormatSymbols( 537 (tuple.locale == null ? EN : tuple.locale).toLocale())); 538 adjustDecimalFormat(tuple, fmt); 539 return fmt; 540 } 541 542 /** 543 * @param tuple 544 * @param fmt 545 */ 546 private void adjustDecimalFormat( 547 DataDrivenNumberFormatTestData tuple, 548 java.text.DecimalFormat fmt) { 549 if (tuple.minIntegerDigits != null) { 550 fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); 551 } 552 if (tuple.maxIntegerDigits != null) { 553 fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); 554 } 555 if (tuple.minFractionDigits != null) { 556 fmt.setMinimumFractionDigits(tuple.minFractionDigits); 557 } 558 if (tuple.maxFractionDigits != null) { 559 fmt.setMaximumFractionDigits(tuple.maxFractionDigits); 560 } 561 if (tuple.currency != null) { 562 fmt.setCurrency(java.util.Currency.getInstance(tuple.currency.toString())); 563 } 564 if (tuple.minGroupingDigits != null) { 565 // Oops we don't support this. 566 } 567 if (tuple.useSigDigits != null) { 568 // Oops we don't support this 569 } 570 if (tuple.minSigDigits != null) { 571 // Oops we don't support this 572 } 573 if (tuple.maxSigDigits != null) { 574 // Oops we don't support this 575 } 576 if (tuple.useGrouping != null) { 577 fmt.setGroupingUsed(tuple.useGrouping != 0); 578 } 579 if (tuple.multiplier != null) { 580 fmt.setMultiplier(tuple.multiplier); 581 } 582 if (tuple.roundingIncrement != null) { 583 // Not supported 584 } 585 if (tuple.formatWidth != null) { 586 // Not supported 587 } 588 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 589 // Not supported 590 } 591 if (tuple.useScientific != null) { 592 // Not supported 593 } 594 if (tuple.grouping != null) { 595 fmt.setGroupingSize(tuple.grouping); 596 } 597 if (tuple.grouping2 != null) { 598 // Not supported 599 } 600 if (tuple.roundingMode != null) { 601 // Not supported 602 } 603 if (tuple.currencyUsage != null) { 604 // Not supported 605 } 606 if (tuple.minimumExponentDigits != null) { 607 // Not supported 608 } 609 if (tuple.exponentSignAlwaysShown != null) { 610 // Not supported 611 } 612 if (tuple.decimalSeparatorAlwaysShown != null) { 613 fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 614 } 615 if (tuple.padPosition != null) { 616 // Not supported 617 } 618 if (tuple.positivePrefix != null) { 619 fmt.setPositivePrefix(tuple.positivePrefix); 620 } 621 if (tuple.positiveSuffix != null) { 622 fmt.setPositiveSuffix(tuple.positiveSuffix); 623 } 624 if (tuple.negativePrefix != null) { 625 fmt.setNegativePrefix(tuple.negativePrefix); 626 } 627 if (tuple.negativeSuffix != null) { 628 fmt.setNegativeSuffix(tuple.negativeSuffix); 629 } 630 if (tuple.signAlwaysShown != null) { 631 // Not supported. 632 } 633 if (tuple.localizedPattern != null) { 634 fmt.applyLocalizedPattern(tuple.localizedPattern); 635 } 636 637 // lenient parsing not supported by JDK 638 if (tuple.parseIntegerOnly != null) { 639 fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 640 } 641 if (tuple.parseCaseSensitive != null) { 642 // Not supported. 643 } 644 if (tuple.decimalPatternMatchRequired != null) { 645 // Oops, not supported 646 } 647 if (tuple.parseNoExponent != null) { 648 // Oops, not supported for now 649 } 650 } 651 }; 652 653 static void propertiesFromTuple( 654 DataDrivenNumberFormatTestData tuple, 655 DecimalFormatProperties properties) { 656 if (tuple.minIntegerDigits != null) { 657 properties.setMinimumIntegerDigits(tuple.minIntegerDigits); 658 } 659 if (tuple.maxIntegerDigits != null) { 660 properties.setMaximumIntegerDigits(tuple.maxIntegerDigits); 661 } 662 if (tuple.minFractionDigits != null) { 663 properties.setMinimumFractionDigits(tuple.minFractionDigits); 664 } 665 if (tuple.maxFractionDigits != null) { 666 properties.setMaximumFractionDigits(tuple.maxFractionDigits); 667 } 668 if (tuple.currency != null) { 669 properties.setCurrency(tuple.currency); 670 } 671 if (tuple.minGroupingDigits != null) { 672 properties.setMinimumGroupingDigits(tuple.minGroupingDigits); 673 } 674 if (tuple.useSigDigits != null) { 675 // TODO 676 } 677 if (tuple.minSigDigits != null) { 678 properties.setMinimumSignificantDigits(tuple.minSigDigits); 679 } 680 if (tuple.maxSigDigits != null) { 681 properties.setMaximumSignificantDigits(tuple.maxSigDigits); 682 } 683 if (tuple.useGrouping != null) { 684 properties.setGroupingUsed(tuple.useGrouping > 0); 685 } 686 if (tuple.multiplier != null) { 687 properties.setMultiplier(new BigDecimal(tuple.multiplier)); 688 } 689 if (tuple.roundingIncrement != null) { 690 properties.setRoundingIncrement(new BigDecimal(tuple.roundingIncrement.toString())); 691 } 692 if (tuple.formatWidth != null) { 693 properties.setFormatWidth(tuple.formatWidth); 694 } 695 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 696 properties.setPadString(tuple.padCharacter.toString()); 697 } 698 if (tuple.useScientific != null) { 699 properties.setMinimumExponentDigits(tuple.useScientific != 0 ? 1 : -1); 700 } 701 if (tuple.grouping != null) { 702 properties.setGroupingSize(tuple.grouping); 703 } 704 if (tuple.grouping2 != null) { 705 properties.setSecondaryGroupingSize(tuple.grouping2); 706 } 707 if (tuple.roundingMode != null) { 708 properties.setRoundingMode(RoundingMode.valueOf(tuple.roundingMode)); 709 } 710 if (tuple.currencyUsage != null) { 711 properties.setCurrencyUsage(tuple.currencyUsage); 712 } 713 if (tuple.minimumExponentDigits != null) { 714 properties.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue()); 715 } 716 if (tuple.exponentSignAlwaysShown != null) { 717 properties.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0); 718 } 719 if (tuple.decimalSeparatorAlwaysShown != null) { 720 properties.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 721 } 722 if (tuple.padPosition != null) { 723 properties.setPadPosition(PadPosition.fromOld(tuple.padPosition)); 724 } 725 if (tuple.positivePrefix != null) { 726 properties.setPositivePrefix(tuple.positivePrefix); 727 } 728 if (tuple.positiveSuffix != null) { 729 properties.setPositiveSuffix(tuple.positiveSuffix); 730 } 731 if (tuple.negativePrefix != null) { 732 properties.setNegativePrefix(tuple.negativePrefix); 733 } 734 if (tuple.negativeSuffix != null) { 735 properties.setNegativeSuffix(tuple.negativeSuffix); 736 } 737 if (tuple.signAlwaysShown != null) { 738 properties.setSignAlwaysShown(tuple.signAlwaysShown != 0); 739 } 740 if (tuple.localizedPattern != null) { 741 DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(tuple.locale); 742 String converted = PatternStringUtils 743 .convertLocalized(tuple.localizedPattern, symbols, false); 744 PatternStringParser.parseToExistingProperties(converted, properties); 745 } 746 if (tuple.lenient != null) { 747 properties.setParseMode(tuple.lenient == 0 ? ParseMode.STRICT : ParseMode.LENIENT); 748 } 749 if (tuple.parseIntegerOnly != null) { 750 properties.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 751 } 752 if (tuple.parseCaseSensitive != null) { 753 properties.setParseCaseSensitive(tuple.parseCaseSensitive != 0); 754 } 755 if (tuple.decimalPatternMatchRequired != null) { 756 properties.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0); 757 } 758 if (tuple.parseNoExponent != null) { 759 properties.setParseNoExponent(tuple.parseNoExponent != 0); 760 } 761 } 762 763 /** 764 * Same as ICU4J, but bypasses the DecimalFormat wrapper and goes directly to the 765 * DecimalFormatProperties. 766 */ 767 private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU4J_Properties = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 768 769 @Override 770 public Character Id() { 771 return 'P'; 772 } 773 774 /** 775 * Runs a single formatting test. On success, returns null. On failure, returns the error. This 776 * implementation just returns null. Subclasses should override. 777 * 778 * @param tuple 779 * contains the parameters of the format test. 780 */ 781 @Override 782 public String format(DataDrivenNumberFormatTestData tuple) { 783 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 784 ULocale locale = (tuple.locale == null) ? ULocale.ENGLISH : tuple.locale; 785 DecimalFormatProperties properties = PatternStringParser.parseToProperties(pattern, 786 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 787 : PatternStringParser.IGNORE_ROUNDING_NEVER); 788 propertiesFromTuple(tuple, properties); 789 DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); 790 LocalizedNumberFormatter fmt = NumberFormatter.fromDecimalFormat(properties, symbols, null) 791 .locale(locale); 792 Number number = toNumber(tuple.format); 793 String expected = tuple.output; 794 String actual = fmt.format(number).toString(); 795 if (!expected.equals(actual)) { 796 return "Expected \"" + expected + "\", got \"" + actual + "\""; 797 } 798 return null; 799 } 800 801 /** 802 * Runs a single toPattern test. On success, returns null. On failure, returns the error. This 803 * implementation just returns null. Subclasses should override. 804 * 805 * @param tuple 806 * contains the parameters of the format test. 807 */ 808 @Override 809 public String toPattern(DataDrivenNumberFormatTestData tuple) { 810 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 811 final DecimalFormatProperties properties; 812 DecimalFormat df; 813 try { 814 properties = PatternStringParser.parseToProperties(pattern, 815 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 816 : PatternStringParser.IGNORE_ROUNDING_NEVER); 817 propertiesFromTuple(tuple, properties); 818 // TODO: Use PatternString.propertiesToString() directly. (How to deal with 819 // CurrencyUsage?) 820 df = new DecimalFormat(); 821 df.setProperties(new PropertySetter() { 822 @Override 823 public void set(DecimalFormatProperties props) { 824 props.copyFrom(properties); 825 } 826 }); 827 } catch (IllegalArgumentException e) { 828 e.printStackTrace(); 829 return e.getLocalizedMessage(); 830 } 831 832 if (tuple.toPattern != null) { 833 String expected = tuple.toPattern; 834 String actual = df.toPattern(); 835 if (!expected.equals(actual)) { 836 return "Expected toPattern='" + expected + "'; got '" + actual + "'"; 837 } 838 } 839 if (tuple.toLocalizedPattern != null) { 840 String expected = tuple.toLocalizedPattern; 841 String actual = PatternStringUtils.propertiesToPatternString(properties); 842 if (!expected.equals(actual)) { 843 return "Expected toLocalizedPattern='" + expected + "'; got '" + actual + "'"; 844 } 845 } 846 return null; 847 } 848 849 @Override 850 public String parse(DataDrivenNumberFormatTestData tuple) { 851 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 852 DecimalFormatProperties properties; 853 ParsePosition ppos = new ParsePosition(0); 854 Number actual; 855 try { 856 properties = PatternStringParser.parseToProperties(pattern, 857 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 858 : PatternStringParser.IGNORE_ROUNDING_NEVER); 859 propertiesFromTuple(tuple, properties); 860 actual = NumberParserImpl.parseStatic(tuple.parse, 861 ppos, 862 properties, 863 DecimalFormatSymbols.getInstance(tuple.locale)); 864 } catch (IllegalArgumentException e) { 865 return "parse exception: " + e.getMessage(); 866 } 867 return compareParseResult(tuple.output, actual, ppos); 868 } 869 870 @Override 871 public String parseCurrency(DataDrivenNumberFormatTestData tuple) { 872 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 873 DecimalFormatProperties properties; 874 ParsePosition ppos = new ParsePosition(0); 875 CurrencyAmount actual; 876 try { 877 properties = PatternStringParser.parseToProperties(pattern, 878 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 879 : PatternStringParser.IGNORE_ROUNDING_NEVER); 880 propertiesFromTuple(tuple, properties); 881 actual = NumberParserImpl.parseStaticCurrency(tuple.parse, 882 ppos, 883 properties, 884 DecimalFormatSymbols.getInstance(tuple.locale)); 885 } catch (IllegalArgumentException e) { 886 e.printStackTrace(); 887 return "parse exception: " + e.getMessage(); 888 } 889 return compareParseCurrencyResult(tuple.output, tuple.outputCurrency, actual, ppos); 890 } 891 }; 892 893 @Test 894 public void TestNoUnknownIDs() { 895 DataDrivenNumberFormatTestUtility.checkNoUnknownIDs("numberformattestspecification.txt", "CHJKP"); 896 } 897 898 @Test 899 public void TestDataDrivenICU4J() { 900 DataDrivenNumberFormatTestUtility 901 .runFormatSuiteIncludingKnownFailures("numberformattestspecification.txt", ICU4J); 902 } 903 904 // Android patch: Android can't access DecimalFormat_ICU58 for testing (b/33448125). 905 /* 906 @Test 907 public void TestDataDrivenICU58() { 908 // Android can't access DecimalFormat_ICU58 for testing (ticket #13283). 909 if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) 910 return; 911 912 DataDrivenNumberFormatTestUtility 913 .runFormatSuiteIncludingKnownFailures("numberformattestspecification.txt", ICU58); 914 } 915 */ 916 // Android patch end. 917 918 @Test 919 public void TestDataDrivenJDK() { 920 // #13373: Since not all JDK implementations are the same, test only whitelisted JDKs 921 // with known behavior. The JDK version should be occasionally updated. 922 org.junit.Assume.assumeTrue(TestUtil.getJavaRuntimeName() == TestUtil.JavaRuntimeName.OpenJDK 923 && TestUtil.getJavaVersion() == 8); 924 925 DataDrivenNumberFormatTestUtility 926 .runFormatSuiteIncludingKnownFailures("numberformattestspecification.txt", JDK); 927 } 928 929 @Test 930 public void TestDataDrivenICU4JProperties() { 931 DataDrivenNumberFormatTestUtility 932 .runFormatSuiteIncludingKnownFailures("numberformattestspecification.txt", ICU4J_Properties); 933 } 934 } 935