1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 1996-2015, International Business Machines Corporation and * 7 * others. All Rights Reserved. * 8 ******************************************************************************* 9 */ 10 package android.icu.text; 11 12 import java.text.FieldPosition; 13 import java.text.ParsePosition; 14 import java.util.List; 15 16 import android.icu.impl.PatternProps; 17 import android.icu.impl.Utility; 18 19 /** 20 * A class representing a single rule in a RuleBasedNumberFormat. A rule 21 * inserts its text into the result string and then passes control to its 22 * substitutions, which do the same thing. 23 */ 24 final class NFRule { 25 //----------------------------------------------------------------------- 26 // constants 27 //----------------------------------------------------------------------- 28 29 /** 30 * Special base value used to identify a negative-number rule 31 */ 32 static final int NEGATIVE_NUMBER_RULE = -1; 33 34 /** 35 * Special base value used to identify an improper fraction (x.x) rule 36 */ 37 static final int IMPROPER_FRACTION_RULE = -2; 38 39 /** 40 * Special base value used to identify a proper fraction (0.x) rule 41 */ 42 static final int PROPER_FRACTION_RULE = -3; 43 44 /** 45 * Special base value used to identify a master rule 46 */ 47 static final int MASTER_RULE = -4; 48 49 /** 50 * Special base value used to identify an infinity rule 51 */ 52 static final int INFINITY_RULE = -5; 53 54 /** 55 * Special base value used to identify a not a number rule 56 */ 57 static final int NAN_RULE = -6; 58 59 static final Long ZERO = (long) 0; 60 61 //----------------------------------------------------------------------- 62 // data members 63 //----------------------------------------------------------------------- 64 65 /** 66 * The rule's base value 67 */ 68 private long baseValue; 69 70 /** 71 * The rule's radix (the radix to the power of the exponent equals 72 * the rule's divisor) 73 */ 74 private int radix = 10; 75 76 /** 77 * The rule's exponent (the radix raised to the power of the exponent 78 * equals the rule's divisor) 79 */ 80 private short exponent = 0; 81 82 /** 83 * If this is a fraction rule, this is the decimal point from DecimalFormatSymbols to match. 84 */ 85 private char decimalPoint = 0; 86 87 /** 88 * The rule's rule text. When formatting a number, the rule's text 89 * is inserted into the result string, and then the text from any 90 * substitutions is inserted into the result string 91 */ 92 private String ruleText = null; 93 94 /** 95 * The rule's plural format when defined. This is not a substitution 96 * because it only works on the current baseValue. It's normally not used 97 * due to the overhead. 98 */ 99 private PluralFormat rulePatternFormat = null; 100 101 /** 102 * The rule's first substitution (the one with the lower offset 103 * into the rule text) 104 */ 105 private NFSubstitution sub1 = null; 106 107 /** 108 * The rule's second substitution (the one with the higher offset 109 * into the rule text) 110 */ 111 private NFSubstitution sub2 = null; 112 113 /** 114 * The RuleBasedNumberFormat that owns this rule 115 */ 116 private final RuleBasedNumberFormat formatter; 117 118 //----------------------------------------------------------------------- 119 // construction 120 //----------------------------------------------------------------------- 121 122 /** 123 * Creates one or more rules based on the description passed in. 124 * @param description The description of the rule(s). 125 * @param owner The rule set containing the new rule(s). 126 * @param predecessor The rule that precedes the new one(s) in "owner"'s 127 * rule list 128 * @param ownersOwner The RuleBasedNumberFormat that owns the 129 * rule set that owns the new rule(s) 130 * @param returnList One or more instances of NFRule are added and returned here 131 */ 132 public static void makeRules(String description, 133 NFRuleSet owner, 134 NFRule predecessor, 135 RuleBasedNumberFormat ownersOwner, 136 List<NFRule> returnList) { 137 // we know we're making at least one rule, so go ahead and 138 // new it up and initialize its basevalue and divisor 139 // (this also strips the rule descriptor, if any, off the 140 // description string) 141 NFRule rule1 = new NFRule(ownersOwner, description); 142 description = rule1.ruleText; 143 144 // check the description to see whether there's text enclosed 145 // in brackets 146 int brack1 = description.indexOf('['); 147 int brack2 = brack1 < 0 ? -1 : description.indexOf(']'); 148 149 // if the description doesn't contain a matched pair of brackets, 150 // or if it's of a type that doesn't recognize bracketed text, 151 // then leave the description alone, initialize the rule's 152 // rule text and substitutions, and return that rule 153 if (brack2 < 0 || brack1 > brack2 154 || rule1.baseValue == PROPER_FRACTION_RULE 155 || rule1.baseValue == NEGATIVE_NUMBER_RULE 156 || rule1.baseValue == INFINITY_RULE 157 || rule1.baseValue == NAN_RULE) 158 { 159 rule1.extractSubstitutions(owner, description, predecessor); 160 } 161 else { 162 // if the description does contain a matched pair of brackets, 163 // then it's really shorthand for two rules (with one exception) 164 NFRule rule2 = null; 165 StringBuilder sbuf = new StringBuilder(); 166 167 // we'll actually only split the rule into two rules if its 168 // base value is an even multiple of its divisor (or it's one 169 // of the special rules) 170 if ((rule1.baseValue > 0 171 && rule1.baseValue % (power(rule1.radix, rule1.exponent)) == 0) 172 || rule1.baseValue == IMPROPER_FRACTION_RULE 173 || rule1.baseValue == MASTER_RULE) 174 { 175 176 // if it passes that test, new up the second rule. If the 177 // rule set both rules will belong to is a fraction rule 178 // set, they both have the same base value; otherwise, 179 // increment the original rule's base value ("rule1" actually 180 // goes SECOND in the rule set's rule list) 181 rule2 = new NFRule(ownersOwner, null); 182 if (rule1.baseValue >= 0) { 183 rule2.baseValue = rule1.baseValue; 184 if (!owner.isFractionSet()) { 185 ++rule1.baseValue; 186 } 187 } 188 else if (rule1.baseValue == IMPROPER_FRACTION_RULE) { 189 // if the description began with "x.x" and contains bracketed 190 // text, it describes both the improper fraction rule and 191 // the proper fraction rule 192 rule2.baseValue = PROPER_FRACTION_RULE; 193 } 194 else if (rule1.baseValue == MASTER_RULE) { 195 // if the description began with "x.0" and contains bracketed 196 // text, it describes both the master rule and the 197 // improper fraction rule 198 rule2.baseValue = rule1.baseValue; 199 rule1.baseValue = IMPROPER_FRACTION_RULE; 200 } 201 202 // both rules have the same radix and exponent (i.e., the 203 // same divisor) 204 rule2.radix = rule1.radix; 205 rule2.exponent = rule1.exponent; 206 207 // rule2's rule text omits the stuff in brackets: initialize 208 // its rule text and substitutions accordingly 209 sbuf.append(description.substring(0, brack1)); 210 if (brack2 + 1 < description.length()) { 211 sbuf.append(description.substring(brack2 + 1)); 212 } 213 rule2.extractSubstitutions(owner, sbuf.toString(), predecessor); 214 } 215 216 // rule1's text includes the text in the brackets but omits 217 // the brackets themselves: initialize _its_ rule text and 218 // substitutions accordingly 219 sbuf.setLength(0); 220 sbuf.append(description.substring(0, brack1)); 221 sbuf.append(description.substring(brack1 + 1, brack2)); 222 if (brack2 + 1 < description.length()) { 223 sbuf.append(description.substring(brack2 + 1)); 224 } 225 rule1.extractSubstitutions(owner, sbuf.toString(), predecessor); 226 227 // if we only have one rule, return it; if we have two, return 228 // a two-element array containing them (notice that rule2 goes 229 // BEFORE rule1 in the list: in all cases, rule2 OMITS the 230 // material in the brackets and rule1 INCLUDES the material 231 // in the brackets) 232 if (rule2 != null) { 233 if (rule2.baseValue >= 0) { 234 returnList.add(rule2); 235 } 236 else { 237 owner.setNonNumericalRule(rule2); 238 } 239 } 240 } 241 if (rule1.baseValue >= 0) { 242 returnList.add(rule1); 243 } 244 else { 245 owner.setNonNumericalRule(rule1); 246 } 247 } 248 249 /** 250 * Nominal constructor for NFRule. Most of the work of constructing 251 * an NFRule is actually performed by makeRules(). 252 */ 253 public NFRule(RuleBasedNumberFormat formatter, String ruleText) { 254 this.formatter = formatter; 255 this.ruleText = ruleText == null ? null : parseRuleDescriptor(ruleText); 256 } 257 258 /** 259 * This function parses the rule's rule descriptor (i.e., the base 260 * value and/or other tokens that precede the rule's rule text 261 * in the description) and sets the rule's base value, radix, and 262 * exponent according to the descriptor. (If the description doesn't 263 * include a rule descriptor, then this function sets everything to 264 * default values and the rule set sets the rule's real base value). 265 * @param description The rule's description 266 * @return If "description" included a rule descriptor, this is 267 * "description" with the descriptor and any trailing whitespace 268 * stripped off. Otherwise; it's "descriptor" unchanged. 269 */ 270 private String parseRuleDescriptor(String description) { 271 String descriptor; 272 273 // the description consists of a rule descriptor and a rule body, 274 // separated by a colon. The rule descriptor is optional. If 275 // it's omitted, just set the base value to 0. 276 int p = description.indexOf(":"); 277 if (p != -1) { 278 // copy the descriptor out into its own string and strip it, 279 // along with any trailing whitespace, out of the original 280 // description 281 descriptor = description.substring(0, p); 282 ++p; 283 while (p < description.length() && PatternProps.isWhiteSpace(description.charAt(p))) { 284 ++p; 285 } 286 description = description.substring(p); 287 288 // check first to see if the rule descriptor matches the token 289 // for one of the special rules. If it does, set the base 290 // value to the correct identifier value 291 int descriptorLength = descriptor.length(); 292 char firstChar = descriptor.charAt(0); 293 char lastChar = descriptor.charAt(descriptorLength - 1); 294 if (firstChar >= '0' && firstChar <= '9' && lastChar != 'x') { 295 // if the rule descriptor begins with a digit, it's a descriptor 296 // for a normal rule 297 long tempValue = 0; 298 char c = 0; 299 p = 0; 300 301 // begin parsing the descriptor: copy digits 302 // into "tempValue", skip periods, commas, and spaces, 303 // stop on a slash or > sign (or at the end of the string), 304 // and throw an exception on any other character 305 while (p < descriptorLength) { 306 c = descriptor.charAt(p); 307 if (c >= '0' && c <= '9') { 308 tempValue = tempValue * 10 + (c - '0'); 309 } 310 else if (c == '/' || c == '>') { 311 break; 312 } 313 else if (!PatternProps.isWhiteSpace(c) && c != ',' && c != '.') { 314 throw new IllegalArgumentException("Illegal character " + c + " in rule descriptor"); 315 } 316 ++p; 317 } 318 319 // Set the rule's base value according to what we parsed 320 setBaseValue(tempValue); 321 322 // if we stopped the previous loop on a slash, we're 323 // now parsing the rule's radix. Again, accumulate digits 324 // in tempValue, skip punctuation, stop on a > mark, and 325 // throw an exception on anything else 326 if (c == '/') { 327 tempValue = 0; 328 ++p; 329 while (p < descriptorLength) { 330 c = descriptor.charAt(p); 331 if (c >= '0' && c <= '9') { 332 tempValue = tempValue * 10 + (c - '0'); 333 } 334 else if (c == '>') { 335 break; 336 } 337 else if (!PatternProps.isWhiteSpace(c) && c != ',' && c != '.') { 338 throw new IllegalArgumentException("Illegal character " + c + " in rule descriptor"); 339 } 340 ++p; 341 } 342 343 // tempValue now contains the rule's radix. Set it 344 // accordingly, and recalculate the rule's exponent 345 radix = (int)tempValue; 346 if (radix == 0) { 347 throw new IllegalArgumentException("Rule can't have radix of 0"); 348 } 349 exponent = expectedExponent(); 350 } 351 352 // if we stopped the previous loop on a > sign, then continue 353 // for as long as we still see > signs. For each one, 354 // decrement the exponent (unless the exponent is already 0). 355 // If we see another character before reaching the end of 356 // the descriptor, that's also a syntax error. 357 if (c == '>') { 358 while (p < descriptorLength) { 359 c = descriptor.charAt(p); 360 if (c == '>' && exponent > 0) { 361 --exponent; 362 } else { 363 throw new IllegalArgumentException("Illegal character in rule descriptor"); 364 } 365 ++p; 366 } 367 } 368 } 369 else if (descriptor.equals("-x")) { 370 setBaseValue(NEGATIVE_NUMBER_RULE); 371 } 372 else if (descriptorLength == 3) { 373 if (firstChar == '0' && lastChar == 'x') { 374 setBaseValue(PROPER_FRACTION_RULE); 375 decimalPoint = descriptor.charAt(1); 376 } 377 else if (firstChar == 'x' && lastChar == 'x') { 378 setBaseValue(IMPROPER_FRACTION_RULE); 379 decimalPoint = descriptor.charAt(1); 380 } 381 else if (firstChar == 'x' && lastChar == '0') { 382 setBaseValue(MASTER_RULE); 383 decimalPoint = descriptor.charAt(1); 384 } 385 else if (descriptor.equals("NaN")) { 386 setBaseValue(NAN_RULE); 387 } 388 else if (descriptor.equals("Inf")) { 389 setBaseValue(INFINITY_RULE); 390 } 391 } 392 } 393 // else use the default base value for now. 394 395 // finally, if the rule body begins with an apostrophe, strip it off 396 // (this is generally used to put whitespace at the beginning of 397 // a rule's rule text) 398 if (description.length() > 0 && description.charAt(0) == '\'') { 399 description = description.substring(1); 400 } 401 402 // return the description with all the stuff we've just waded through 403 // stripped off the front. It now contains just the rule body. 404 return description; 405 } 406 407 /** 408 * Searches the rule's rule text for the substitution tokens, 409 * creates the substitutions, and removes the substitution tokens 410 * from the rule's rule text. 411 * @param owner The rule set containing this rule 412 * @param predecessor The rule preceding this one in "owners" rule list 413 * @param ruleText The rule text 414 */ 415 private void extractSubstitutions(NFRuleSet owner, 416 String ruleText, 417 NFRule predecessor) { 418 this.ruleText = ruleText; 419 sub1 = extractSubstitution(owner, predecessor); 420 if (sub1 == null) { 421 // Small optimization. There is no need to create a redundant NullSubstitution. 422 sub2 = null; 423 } 424 else { 425 sub2 = extractSubstitution(owner, predecessor); 426 } 427 ruleText = this.ruleText; 428 int pluralRuleStart = ruleText.indexOf("$("); 429 int pluralRuleEnd = (pluralRuleStart >= 0 ? ruleText.indexOf(")$", pluralRuleStart) : -1); 430 if (pluralRuleEnd >= 0) { 431 int endType = ruleText.indexOf(',', pluralRuleStart); 432 if (endType < 0) { 433 throw new IllegalArgumentException("Rule \"" + ruleText + "\" does not have a defined type"); 434 } 435 String type = this.ruleText.substring(pluralRuleStart + 2, endType); 436 PluralRules.PluralType pluralType; 437 if ("cardinal".equals(type)) { 438 pluralType = PluralRules.PluralType.CARDINAL; 439 } 440 else if ("ordinal".equals(type)) { 441 pluralType = PluralRules.PluralType.ORDINAL; 442 } 443 else { 444 throw new IllegalArgumentException(type + " is an unknown type"); 445 } 446 rulePatternFormat = formatter.createPluralFormat(pluralType, 447 ruleText.substring(endType + 1, pluralRuleEnd)); 448 } 449 } 450 451 /** 452 * Searches the rule's rule text for the first substitution token, 453 * creates a substitution based on it, and removes the token from 454 * the rule's rule text. 455 * @param owner The rule set containing this rule 456 * @param predecessor The rule preceding this one in the rule set's 457 * rule list 458 * @return The newly-created substitution. This is never null; if 459 * the rule text doesn't contain any substitution tokens, this will 460 * be a NullSubstitution. 461 */ 462 private NFSubstitution extractSubstitution(NFRuleSet owner, 463 NFRule predecessor) { 464 NFSubstitution result; 465 int subStart; 466 int subEnd; 467 468 // search the rule's rule text for the first two characters of 469 // a substitution token 470 subStart = indexOfAnyRulePrefix(ruleText); 471 472 // if we didn't find one, create a null substitution positioned 473 // at the end of the rule text 474 if (subStart == -1) { 475 return null; 476 } 477 478 // special-case the ">>>" token, since searching for the > at the 479 // end will actually find the > in the middle 480 if (ruleText.startsWith(">>>", subStart)) { 481 subEnd = subStart + 2; 482 } 483 else { 484 // otherwise the substitution token ends with the same character 485 // it began with 486 char c = ruleText.charAt(subStart); 487 subEnd = ruleText.indexOf(c, subStart + 1); 488 // special case for '<%foo<<' 489 if (c == '<' && subEnd != -1 && subEnd < ruleText.length() - 1 && ruleText.charAt(subEnd+1) == c) { 490 // ordinals use "=#,##0==%abbrev=" as their rule. Notice that the '==' in the middle 491 // occurs because of the juxtaposition of two different rules. The check for '<' is a hack 492 // to get around this. Having the duplicate at the front would cause problems with 493 // rules like "<<%" to format, say, percents... 494 ++subEnd; 495 } 496 } 497 498 // if we don't find the end of the token (i.e., if we're on a single, 499 // unmatched token character), create a null substitution positioned 500 // at the end of the rule 501 if (subEnd == -1) { 502 return null; 503 } 504 505 // if we get here, we have a real substitution token (or at least 506 // some text bounded by substitution token characters). Use 507 // makeSubstitution() to create the right kind of substitution 508 result = NFSubstitution.makeSubstitution(subStart, this, predecessor, owner, 509 this.formatter, ruleText.substring(subStart, subEnd + 1)); 510 511 // remove the substitution from the rule text 512 ruleText = ruleText.substring(0, subStart) + ruleText.substring(subEnd + 1); 513 return result; 514 } 515 516 /** 517 * Sets the rule's base value, and causes the radix and exponent 518 * to be recalculated. This is used during construction when we 519 * don't know the rule's base value until after it's been 520 * constructed. It should not be used at any other time. 521 * @param newBaseValue The new base value for the rule. 522 */ 523 final void setBaseValue(long newBaseValue) { 524 // set the base value 525 baseValue = newBaseValue; 526 radix = 10; 527 528 // if this isn't a special rule, recalculate the radix and exponent 529 // (the radix always defaults to 10; if it's supposed to be something 530 // else, it's cleaned up by the caller and the exponent is 531 // recalculated again-- the only function that does this is 532 // NFRule.parseRuleDescriptor() ) 533 if (baseValue >= 1) { 534 exponent = expectedExponent(); 535 536 // this function gets called on a fully-constructed rule whose 537 // description didn't specify a base value. This means it 538 // has substitutions, and some substitutions hold on to copies 539 // of the rule's divisor. Fix their copies of the divisor. 540 if (sub1 != null) { 541 sub1.setDivisor(radix, exponent); 542 } 543 if (sub2 != null) { 544 sub2.setDivisor(radix, exponent); 545 } 546 } 547 else { 548 // if this is a special rule, its radix and exponent are basically 549 // ignored. Set them to "safe" default values 550 exponent = 0; 551 } 552 } 553 554 /** 555 * This calculates the rule's exponent based on its radix and base 556 * value. This will be the highest power the radix can be raised to 557 * and still produce a result less than or equal to the base value. 558 */ 559 private short expectedExponent() { 560 // since the log of 0, or the log base 0 of something, causes an 561 // error, declare the exponent in these cases to be 0 (we also 562 // deal with the special-rule identifiers here) 563 if (radix == 0 || baseValue < 1) { 564 return 0; 565 } 566 567 // we get rounding error in some cases-- for example, log 1000 / log 10 568 // gives us 1.9999999996 instead of 2. The extra logic here is to take 569 // that into account 570 short tempResult = (short)(Math.log(baseValue) / Math.log(radix)); 571 if (power(radix, (short)(tempResult + 1)) <= baseValue) { 572 return (short)(tempResult + 1); 573 } else { 574 return tempResult; 575 } 576 } 577 578 private static final String[] RULE_PREFIXES = new String[] { 579 "<<", "<%", "<#", "<0", 580 ">>", ">%", ">#", ">0", 581 "=%", "=#", "=0" 582 }; 583 584 /** 585 * Searches the rule's rule text for any of the specified strings. 586 * @return The index of the first match in the rule's rule text 587 * (i.e., the first substring in the rule's rule text that matches 588 * _any_ of the strings in "strings"). If none of the strings in 589 * "strings" is found in the rule's rule text, returns -1. 590 */ 591 private static int indexOfAnyRulePrefix(String ruleText) { 592 int result = -1; 593 if (ruleText.length() > 0) { 594 int pos; 595 for (String string : RULE_PREFIXES) { 596 pos = ruleText.indexOf(string); 597 if (pos != -1 && (result == -1 || pos < result)) { 598 result = pos; 599 } 600 } 601 } 602 return result; 603 } 604 605 //----------------------------------------------------------------------- 606 // boilerplate 607 //----------------------------------------------------------------------- 608 609 /** 610 * Tests two rules for equality. 611 * @param that The rule to compare this one against 612 * @return True if the two rules are functionally equivalent 613 */ 614 public boolean equals(Object that) { 615 if (that instanceof NFRule) { 616 NFRule that2 = (NFRule)that; 617 618 return baseValue == that2.baseValue 619 && radix == that2.radix 620 && exponent == that2.exponent 621 && ruleText.equals(that2.ruleText) 622 && Utility.objectEquals(sub1, that2.sub1) 623 && Utility.objectEquals(sub2, that2.sub2); 624 } 625 return false; 626 } 627 628 public int hashCode() { 629 assert false : "hashCode not designed"; 630 return 42; 631 } 632 633 /** 634 * Returns a textual representation of the rule. This won't 635 * necessarily be the same as the description that this rule 636 * was created with, but it will produce the same result. 637 * @return A textual description of the rule 638 */ 639 public String toString() { 640 StringBuilder result = new StringBuilder(); 641 642 // start with the rule descriptor. Special-case the special rules 643 if (baseValue == NEGATIVE_NUMBER_RULE) { 644 result.append("-x: "); 645 } 646 else if (baseValue == IMPROPER_FRACTION_RULE) { 647 result.append('x').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: "); 648 } 649 else if (baseValue == PROPER_FRACTION_RULE) { 650 result.append('0').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: "); 651 } 652 else if (baseValue == MASTER_RULE) { 653 result.append('x').append(decimalPoint == 0 ? '.' : decimalPoint).append("0: "); 654 } 655 else if (baseValue == INFINITY_RULE) { 656 result.append("Inf: "); 657 } 658 else if (baseValue == NAN_RULE) { 659 result.append("NaN: "); 660 } 661 else { 662 // for a normal rule, write out its base value, and if the radix is 663 // something other than 10, write out the radix (with the preceding 664 // slash, of course). Then calculate the expected exponent and if 665 // if isn't the same as the actual exponent, write an appropriate 666 // number of > signs. Finally, terminate the whole thing with 667 // a colon. 668 result.append(String.valueOf(baseValue)); 669 if (radix != 10) { 670 result.append('/').append(radix); 671 } 672 int numCarets = expectedExponent() - exponent; 673 for (int i = 0; i < numCarets; i++) 674 result.append('>'); 675 result.append(": "); 676 } 677 678 // if the rule text begins with a space, write an apostrophe 679 // (whitespace after the rule descriptor is ignored; the 680 // apostrophe is used to make the whitespace significant) 681 if (ruleText.startsWith(" ") && (sub1 == null || sub1.getPos() != 0)) { 682 result.append('\''); 683 } 684 685 // now, write the rule's rule text, inserting appropriate 686 // substitution tokens in the appropriate places 687 StringBuilder ruleTextCopy = new StringBuilder(ruleText); 688 if (sub2 != null) { 689 ruleTextCopy.insert(sub2.getPos(), sub2.toString()); 690 } 691 if (sub1 != null) { 692 ruleTextCopy.insert(sub1.getPos(), sub1.toString()); 693 } 694 result.append(ruleTextCopy.toString()); 695 696 // and finally, top the whole thing off with a semicolon and 697 // return the result 698 result.append(';'); 699 return result.toString(); 700 } 701 702 //----------------------------------------------------------------------- 703 // simple accessors 704 //----------------------------------------------------------------------- 705 706 /** 707 * Returns the rule's base value 708 * @return The rule's base value 709 */ 710 public final char getDecimalPoint() { 711 return decimalPoint; 712 } 713 714 /** 715 * Returns the rule's base value 716 * @return The rule's base value 717 */ 718 public final long getBaseValue() { 719 return baseValue; 720 } 721 722 /** 723 * Returns the rule's divisor (the value that cotrols the behavior 724 * of its substitutions) 725 * @return The rule's divisor 726 */ 727 public long getDivisor() { 728 return power(radix, exponent); 729 } 730 731 //----------------------------------------------------------------------- 732 // formatting 733 //----------------------------------------------------------------------- 734 735 /** 736 * Formats the number, and inserts the resulting text into 737 * toInsertInto. 738 * @param number The number being formatted 739 * @param toInsertInto The string where the resultant text should 740 * be inserted 741 * @param pos The position in toInsertInto where the resultant text 742 * should be inserted 743 */ 744 public void doFormat(long number, StringBuilder toInsertInto, int pos, int recursionCount) { 745 // first, insert the rule's rule text into toInsertInto at the 746 // specified position, then insert the results of the substitutions 747 // into the right places in toInsertInto (notice we do the 748 // substitutions in reverse order so that the offsets don't get 749 // messed up) 750 int pluralRuleStart = ruleText.length(); 751 int lengthOffset = 0; 752 if (rulePatternFormat == null) { 753 toInsertInto.insert(pos, ruleText); 754 } 755 else { 756 pluralRuleStart = ruleText.indexOf("$("); 757 int pluralRuleEnd = ruleText.indexOf(")$", pluralRuleStart); 758 int initialLength = toInsertInto.length(); 759 if (pluralRuleEnd < ruleText.length() - 1) { 760 toInsertInto.insert(pos, ruleText.substring(pluralRuleEnd + 2)); 761 } 762 toInsertInto.insert(pos, rulePatternFormat.format(number / power(radix, exponent))); 763 if (pluralRuleStart > 0) { 764 toInsertInto.insert(pos, ruleText.substring(0, pluralRuleStart)); 765 } 766 lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength); 767 } 768 if (sub2 != null) { 769 sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 770 } 771 if (sub1 != null) { 772 sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 773 } 774 } 775 776 /** 777 * Formats the number, and inserts the resulting text into 778 * toInsertInto. 779 * @param number The number being formatted 780 * @param toInsertInto The string where the resultant text should 781 * be inserted 782 * @param pos The position in toInsertInto where the resultant text 783 * should be inserted 784 */ 785 public void doFormat(double number, StringBuilder toInsertInto, int pos, int recursionCount) { 786 // first, insert the rule's rule text into toInsertInto at the 787 // specified position, then insert the results of the substitutions 788 // into the right places in toInsertInto 789 // [again, we have two copies of this routine that do the same thing 790 // so that we don't sacrifice precision in a long by casting it 791 // to a double] 792 int pluralRuleStart = ruleText.length(); 793 int lengthOffset = 0; 794 if (rulePatternFormat == null) { 795 toInsertInto.insert(pos, ruleText); 796 } 797 else { 798 pluralRuleStart = ruleText.indexOf("$("); 799 int pluralRuleEnd = ruleText.indexOf(")$", pluralRuleStart); 800 int initialLength = toInsertInto.length(); 801 if (pluralRuleEnd < ruleText.length() - 1) { 802 toInsertInto.insert(pos, ruleText.substring(pluralRuleEnd + 2)); 803 } 804 double pluralVal = number; 805 if (0 <= pluralVal && pluralVal < 1) { 806 // We're in a fractional rule, and we have to match the NumeratorSubstitution behavior. 807 // 2.3 can become 0.2999999999999998 for the fraction due to rounding errors. 808 pluralVal = Math.round(pluralVal * power(radix, exponent)); 809 } 810 else { 811 pluralVal = pluralVal / power(radix, exponent); 812 } 813 toInsertInto.insert(pos, rulePatternFormat.format((long)(pluralVal))); 814 if (pluralRuleStart > 0) { 815 toInsertInto.insert(pos, ruleText.substring(0, pluralRuleStart)); 816 } 817 lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength); 818 } 819 if (sub2 != null) { 820 sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 821 } 822 if (sub1 != null) { 823 sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 824 } 825 } 826 827 /** 828 * This is an equivalent to Math.pow that accurately works on 64-bit numbers 829 * @param base The base 830 * @param exponent The exponent 831 * @return radix ** exponent 832 * @see Math#pow(double, double) 833 */ 834 static long power(long base, short exponent) { 835 if (exponent < 0) { 836 throw new IllegalArgumentException("Exponent can not be negative"); 837 } 838 if (base < 0) { 839 throw new IllegalArgumentException("Base can not be negative"); 840 } 841 long result = 1; 842 while (exponent > 0) { 843 if ((exponent & 1) == 1) { 844 result *= base; 845 } 846 base *= base; 847 exponent >>= 1; 848 } 849 return result; 850 } 851 852 /** 853 * Used by the owning rule set to determine whether to invoke the 854 * rollback rule (i.e., whether this rule or the one that precedes 855 * it in the rule set's list should be used to format the number) 856 * @param number The number being formatted 857 * @return True if the rule set should use the rule that precedes 858 * this one in its list; false if it should use this rule 859 */ 860 public boolean shouldRollBack(long number) { 861 // we roll back if the rule contains a modulus substitution, 862 // the number being formatted is an even multiple of the rule's 863 // divisor, and the rule's base value is NOT an even multiple 864 // of its divisor 865 // In other words, if the original description had 866 // 100: << hundred[ >>]; 867 // that expands into 868 // 100: << hundred; 869 // 101: << hundred >>; 870 // internally. But when we're formatting 200, if we use the rule 871 // at 101, which would normally apply, we get "two hundred zero". 872 // To prevent this, we roll back and use the rule at 100 instead. 873 // This is the logic that makes this happen: the rule at 101 has 874 // a modulus substitution, its base value isn't an even multiple 875 // of 100, and the value we're trying to format _is_ an even 876 // multiple of 100. This is called the "rollback rule." 877 if (!((sub1 != null && sub1.isModulusSubstitution()) || (sub2 != null && sub2.isModulusSubstitution()))) { 878 return false; 879 } 880 long divisor = power(radix, exponent); 881 return (number % divisor) == 0 && (baseValue % divisor) != 0; 882 } 883 884 //----------------------------------------------------------------------- 885 // parsing 886 //----------------------------------------------------------------------- 887 888 /** 889 * Attempts to parse the string with this rule. 890 * @param text The string being parsed 891 * @param parsePosition On entry, the value is ignored and assumed to 892 * be 0. On exit, this has been updated with the position of the first 893 * character not consumed by matching the text against this rule 894 * (if this rule doesn't match the text at all, the parse position 895 * if left unchanged (presumably at 0) and the function returns 896 * new Long(0)). 897 * @param isFractionRule True if this rule is contained within a 898 * fraction rule set. This is only used if the rule has no 899 * substitutions. 900 * @return If this rule matched the text, this is the rule's base value 901 * combined appropriately with the results of parsing the substitutions. 902 * If nothing matched, this is new Long(0) and the parse position is 903 * left unchanged. The result will be an instance of Long if the 904 * result is an integer and Double otherwise. The result is never null. 905 */ 906 public Number doParse(String text, ParsePosition parsePosition, boolean isFractionRule, 907 double upperBound) { 908 909 // internally we operate on a copy of the string being parsed 910 // (because we're going to change it) and use our own ParsePosition 911 ParsePosition pp = new ParsePosition(0); 912 913 // check to see whether the text before the first substitution 914 // matches the text at the beginning of the string being 915 // parsed. If it does, strip that off the front of workText; 916 // otherwise, dump out with a mismatch 917 int sub1Pos = sub1 != null ? sub1.getPos() : ruleText.length(); 918 int sub2Pos = sub2 != null ? sub2.getPos() : ruleText.length(); 919 String workText = stripPrefix(text, ruleText.substring(0, sub1Pos), pp); 920 int prefixLength = text.length() - workText.length(); 921 922 if (pp.getIndex() == 0 && sub1Pos != 0) { 923 // commented out because ParsePosition doesn't have error index in 1.1.x 924 // parsePosition.setErrorIndex(pp.getErrorIndex()); 925 return ZERO; 926 } 927 if (baseValue == INFINITY_RULE) { 928 // If you match this, don't try to perform any calculations on it. 929 parsePosition.setIndex(pp.getIndex()); 930 return Double.POSITIVE_INFINITY; 931 } 932 if (baseValue == NAN_RULE) { 933 // If you match this, don't try to perform any calculations on it. 934 parsePosition.setIndex(pp.getIndex()); 935 return Double.NaN; 936 } 937 938 // this is the fun part. The basic guts of the rule-matching 939 // logic is matchToDelimiter(), which is called twice. The first 940 // time it searches the input string for the rule text BETWEEN 941 // the substitutions and tries to match the intervening text 942 // in the input string with the first substitution. If that 943 // succeeds, it then calls it again, this time to look for the 944 // rule text after the second substitution and to match the 945 // intervening input text against the second substitution. 946 // 947 // For example, say we have a rule that looks like this: 948 // first << middle >> last; 949 // and input text that looks like this: 950 // first one middle two last 951 // First we use stripPrefix() to match "first " in both places and 952 // strip it off the front, leaving 953 // one middle two last 954 // Then we use matchToDelimiter() to match " middle " and try to 955 // match "one" against a substitution. If it's successful, we now 956 // have 957 // two last 958 // We use matchToDelimiter() a second time to match " last" and 959 // try to match "two" against a substitution. If "two" matches 960 // the substitution, we have a successful parse. 961 // 962 // Since it's possible in many cases to find multiple instances 963 // of each of these pieces of rule text in the input string, 964 // we need to try all the possible combinations of these 965 // locations. This prevents us from prematurely declaring a mismatch, 966 // and makes sure we match as much input text as we can. 967 int highWaterMark = 0; 968 double result = 0; 969 int start = 0; 970 double tempBaseValue = Math.max(0, baseValue); 971 972 do { 973 // our partial parse result starts out as this rule's base 974 // value. If it finds a successful match, matchToDelimiter() 975 // will compose this in some way with what it gets back from 976 // the substitution, giving us a new partial parse result 977 pp.setIndex(0); 978 double partialResult = matchToDelimiter(workText, start, tempBaseValue, 979 ruleText.substring(sub1Pos, sub2Pos), rulePatternFormat, 980 pp, sub1, upperBound).doubleValue(); 981 982 // if we got a successful match (or were trying to match a 983 // null substitution), pp is now pointing at the first unmatched 984 // character. Take note of that, and try matchToDelimiter() 985 // on the input text again 986 if (pp.getIndex() != 0 || sub1 == null) { 987 start = pp.getIndex(); 988 989 String workText2 = workText.substring(pp.getIndex()); 990 ParsePosition pp2 = new ParsePosition(0); 991 992 // the second matchToDelimiter() will compose our previous 993 // partial result with whatever it gets back from its 994 // substitution if there's a successful match, giving us 995 // a real result 996 partialResult = matchToDelimiter(workText2, 0, partialResult, 997 ruleText.substring(sub2Pos), rulePatternFormat, pp2, sub2, 998 upperBound).doubleValue(); 999 1000 // if we got a successful match on this second 1001 // matchToDelimiter() call, update the high-water mark 1002 // and result (if necessary) 1003 if (pp2.getIndex() != 0 || sub2 == null) { 1004 if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) { 1005 highWaterMark = prefixLength + pp.getIndex() + pp2.getIndex(); 1006 result = partialResult; 1007 } 1008 } 1009 // commented out because ParsePosition doesn't have error index in 1.1.x 1010 // else { 1011 // int temp = pp2.getErrorIndex() + sub1.getPos() + pp.getIndex(); 1012 // if (temp> parsePosition.getErrorIndex()) { 1013 // parsePosition.setErrorIndex(temp); 1014 // } 1015 // } 1016 } 1017 // commented out because ParsePosition doesn't have error index in 1.1.x 1018 // else { 1019 // int temp = sub1.getPos() + pp.getErrorIndex(); 1020 // if (temp > parsePosition.getErrorIndex()) { 1021 // parsePosition.setErrorIndex(temp); 1022 // } 1023 // } 1024 // keep trying to match things until the outer matchToDelimiter() 1025 // call fails to make a match (each time, it picks up where it 1026 // left off the previous time) 1027 } 1028 while (sub1Pos != sub2Pos && pp.getIndex() > 0 && pp.getIndex() 1029 < workText.length() && pp.getIndex() != start); 1030 1031 // update the caller's ParsePosition with our high-water mark 1032 // (i.e., it now points at the first character this function 1033 // didn't match-- the ParsePosition is therefore unchanged if 1034 // we didn't match anything) 1035 parsePosition.setIndex(highWaterMark); 1036 // commented out because ParsePosition doesn't have error index in 1.1.x 1037 // if (highWaterMark > 0) { 1038 // parsePosition.setErrorIndex(0); 1039 // } 1040 1041 // this is a hack for one unusual condition: Normally, whether this 1042 // rule belong to a fraction rule set or not is handled by its 1043 // substitutions. But if that rule HAS NO substitutions, then 1044 // we have to account for it here. By definition, if the matching 1045 // rule in a fraction rule set has no substitutions, its numerator 1046 // is 1, and so the result is the reciprocal of its base value. 1047 if (isFractionRule && highWaterMark > 0 && sub1 == null) { 1048 result = 1 / result; 1049 } 1050 1051 // return the result as a Long if possible, or as a Double 1052 if (result == (long)result) { 1053 return Long.valueOf((long)result); 1054 } else { 1055 return new Double(result); 1056 } 1057 } 1058 1059 /** 1060 * This function is used by parse() to match the text being parsed 1061 * against a possible prefix string. This function 1062 * matches characters from the beginning of the string being parsed 1063 * to characters from the prospective prefix. If they match, pp is 1064 * updated to the first character not matched, and the result is 1065 * the unparsed part of the string. If they don't match, the whole 1066 * string is returned, and pp is left unchanged. 1067 * @param text The string being parsed 1068 * @param prefix The text to match against 1069 * @param pp On entry, ignored and assumed to be 0. On exit, points 1070 * to the first unmatched character (assuming the whole prefix matched), 1071 * or is unchanged (if the whole prefix didn't match). 1072 * @return If things match, this is the unparsed part of "text"; 1073 * if they didn't match, this is "text". 1074 */ 1075 private String stripPrefix(String text, String prefix, ParsePosition pp) { 1076 // if the prefix text is empty, dump out without doing anything 1077 if (prefix.length() == 0) { 1078 return text; 1079 } else { 1080 // otherwise, use prefixLength() to match the beginning of 1081 // "text" against "prefix". This function returns the 1082 // number of characters from "text" that matched (or 0 if 1083 // we didn't match the whole prefix) 1084 int pfl = prefixLength(text, prefix); 1085 if (pfl != 0) { 1086 // if we got a successful match, update the parse position 1087 // and strip the prefix off of "text" 1088 pp.setIndex(pp.getIndex() + pfl); 1089 return text.substring(pfl); 1090 1091 // if we didn't get a successful match, leave everything alone 1092 } else { 1093 return text; 1094 } 1095 } 1096 } 1097 1098 /** 1099 * Used by parse() to match a substitution and any following text. 1100 * "text" is searched for instances of "delimiter". For each instance 1101 * of delimiter, the intervening text is tested to see whether it 1102 * matches the substitution. The longest match wins. 1103 * @param text The string being parsed 1104 * @param startPos The position in "text" where we should start looking 1105 * for "delimiter". 1106 * @param baseVal A partial parse result (often the rule's base value), 1107 * which is combined with the result from matching the substitution 1108 * @param delimiter The string to search "text" for. 1109 * @param pp Ignored and presumed to be 0 on entry. If there's a match, 1110 * on exit this will point to the first unmatched character. 1111 * @param sub If we find "delimiter" in "text", this substitution is used 1112 * to match the text between the beginning of the string and the 1113 * position of "delimiter." (If "delimiter" is the empty string, then 1114 * this function just matches against this substitution and updates 1115 * everything accordingly.) 1116 * @param upperBound When matching the substitution, it will only 1117 * consider rules with base values lower than this value. 1118 * @return If there's a match, this is the result of composing 1119 * baseValue with the result of matching the substitution. Otherwise, 1120 * this is new Long(0). It's never null. If the result is an integer, 1121 * this will be an instance of Long; otherwise, it's an instance of 1122 * Double. 1123 */ 1124 private Number matchToDelimiter(String text, int startPos, double baseVal, 1125 String delimiter, PluralFormat pluralFormatDelimiter, ParsePosition pp, NFSubstitution sub, double upperBound) { 1126 // if "delimiter" contains real (i.e., non-ignorable) text, search 1127 // it for "delimiter" beginning at "start". If that succeeds, then 1128 // use "sub"'s doParse() method to match the text before the 1129 // instance of "delimiter" we just found. 1130 if (!allIgnorable(delimiter)) { 1131 ParsePosition tempPP = new ParsePosition(0); 1132 Number tempResult; 1133 1134 // use findText() to search for "delimiter". It returns a two- 1135 // element array: element 0 is the position of the match, and 1136 // element 1 is the number of characters that matched 1137 // "delimiter". 1138 int[] temp = findText(text, delimiter, pluralFormatDelimiter, startPos); 1139 int dPos = temp[0]; 1140 int dLen = temp[1]; 1141 1142 // if findText() succeeded, isolate the text preceding the 1143 // match, and use "sub" to match that text 1144 while (dPos >= 0) { 1145 String subText = text.substring(0, dPos); 1146 if (subText.length() > 0) { 1147 tempResult = sub.doParse(subText, tempPP, baseVal, upperBound, 1148 formatter.lenientParseEnabled()); 1149 1150 // if the substitution could match all the text up to 1151 // where we found "delimiter", then this function has 1152 // a successful match. Bump the caller's parse position 1153 // to point to the first character after the text 1154 // that matches "delimiter", and return the result 1155 // we got from parsing the substitution. 1156 if (tempPP.getIndex() == dPos) { 1157 pp.setIndex(dPos + dLen); 1158 return tempResult; 1159 } 1160 // commented out because ParsePosition doesn't have error index in 1.1.x 1161 // else { 1162 // if (tempPP.getErrorIndex() > 0) { 1163 // pp.setErrorIndex(tempPP.getErrorIndex()); 1164 // } else { 1165 // pp.setErrorIndex(tempPP.getIndex()); 1166 // } 1167 // } 1168 } 1169 1170 // if we didn't match the substitution, search for another 1171 // copy of "delimiter" in "text" and repeat the loop if 1172 // we find it 1173 tempPP.setIndex(0); 1174 temp = findText(text, delimiter, pluralFormatDelimiter, dPos + dLen); 1175 dPos = temp[0]; 1176 dLen = temp[1]; 1177 } 1178 // if we make it here, this was an unsuccessful match, and we 1179 // leave pp unchanged and return 0 1180 pp.setIndex(0); 1181 return ZERO; 1182 1183 // if "delimiter" is empty, or consists only of ignorable characters 1184 // (i.e., is semantically empty), thwe we obviously can't search 1185 // for "delimiter". Instead, just use "sub" to parse as much of 1186 // "text" as possible. 1187 } 1188 else if (sub == null) { 1189 return baseVal; 1190 } 1191 else { 1192 ParsePosition tempPP = new ParsePosition(0); 1193 Number result = ZERO; 1194 // try to match the whole string against the substitution 1195 Number tempResult = sub.doParse(text, tempPP, baseVal, upperBound, 1196 formatter.lenientParseEnabled()); 1197 if (tempPP.getIndex() != 0) { 1198 // if there's a successful match (or it's a null 1199 // substitution), update pp to point to the first 1200 // character we didn't match, and pass the result from 1201 // sub.doParse() on through to the caller 1202 pp.setIndex(tempPP.getIndex()); 1203 if (tempResult != null) { 1204 result = tempResult; 1205 } 1206 } 1207 // commented out because ParsePosition doesn't have error index in 1.1.x 1208 // else { 1209 // pp.setErrorIndex(tempPP.getErrorIndex()); 1210 // } 1211 1212 // and if we get to here, then nothing matched, so we return 1213 // 0 and leave pp alone 1214 return result; 1215 } 1216 } 1217 1218 /** 1219 * Used by stripPrefix() to match characters. If lenient parse mode 1220 * is off, this just calls startsWith(). If lenient parse mode is on, 1221 * this function uses CollationElementIterators to match characters in 1222 * the strings (only primary-order differences are significant in 1223 * determining whether there's a match). 1224 * @param str The string being tested 1225 * @param prefix The text we're hoping to see at the beginning 1226 * of "str" 1227 * @return If "prefix" is found at the beginning of "str", this 1228 * is the number of characters in "str" that were matched (this 1229 * isn't necessarily the same as the length of "prefix" when matching 1230 * text with a collator). If there's no match, this is 0. 1231 */ 1232 private int prefixLength(String str, String prefix) { 1233 // if we're looking for an empty prefix, it obviously matches 1234 // zero characters. Just go ahead and return 0. 1235 if (prefix.length() == 0) { 1236 return 0; 1237 } 1238 1239 RbnfLenientScanner scanner = formatter.getLenientScanner(); 1240 if (scanner != null) { 1241 return scanner.prefixLength(str, prefix); 1242 } 1243 1244 // If lenient parsing is turned off, forget all that crap above. 1245 // Just use String.startsWith() and be done with it. 1246 if (str.startsWith(prefix)) { 1247 return prefix.length(); 1248 } 1249 return 0; 1250 } 1251 1252 /** 1253 * Searches a string for another string. If lenient parsing is off, 1254 * this just calls indexOf(). If lenient parsing is on, this function 1255 * uses CollationElementIterator to match characters, and only 1256 * primary-order differences are significant in determining whether 1257 * there's a match. 1258 * @param str The string to search 1259 * @param key The string to search "str" for 1260 * @param startingAt The index into "str" where the search is to 1261 * begin 1262 * @return A two-element array of ints. Element 0 is the position 1263 * of the match, or -1 if there was no match. Element 1 is the 1264 * number of characters in "str" that matched (which isn't necessarily 1265 * the same as the length of "key") 1266 */ 1267 private int[] findText(String str, String key, PluralFormat pluralFormatKey, int startingAt) { 1268 RbnfLenientScanner scanner = formatter.getLenientScanner(); 1269 if (pluralFormatKey != null) { 1270 FieldPosition position = new FieldPosition(NumberFormat.INTEGER_FIELD); 1271 position.setBeginIndex(startingAt); 1272 pluralFormatKey.parseType(str, scanner, position); 1273 int start = position.getBeginIndex(); 1274 if (start >= 0) { 1275 int pluralRuleStart = ruleText.indexOf("$("); 1276 int pluralRuleSuffix = ruleText.indexOf(")$", pluralRuleStart) + 2; 1277 int matchLen = position.getEndIndex() - start; 1278 String prefix = ruleText.substring(0, pluralRuleStart); 1279 String suffix = ruleText.substring(pluralRuleSuffix); 1280 if (str.regionMatches(start - prefix.length(), prefix, 0, prefix.length()) 1281 && str.regionMatches(start + matchLen, suffix, 0, suffix.length())) 1282 { 1283 return new int[]{start - prefix.length(), matchLen + prefix.length() + suffix.length()}; 1284 } 1285 } 1286 return new int[]{-1, 0}; 1287 } 1288 1289 if (scanner != null) { 1290 // if lenient parsing is turned ON, we've got some work 1291 // ahead of us 1292 return scanner.findText(str, key, startingAt); 1293 } 1294 // if lenient parsing is turned off, this is easy. Just call 1295 // String.indexOf() and we're done 1296 return new int[]{str.indexOf(key, startingAt), key.length()}; 1297 } 1298 1299 /** 1300 * Checks to see whether a string consists entirely of ignorable 1301 * characters. 1302 * @param str The string to test. 1303 * @return true if the string is empty of consists entirely of 1304 * characters that the number formatter's collator says are 1305 * ignorable at the primary-order level. false otherwise. 1306 */ 1307 private boolean allIgnorable(String str) { 1308 // if the string is empty, we can just return true 1309 if (str == null || str.length() == 0) { 1310 return true; 1311 } 1312 RbnfLenientScanner scanner = formatter.getLenientScanner(); 1313 return scanner != null && scanner.allIgnorable(str); 1314 } 1315 1316 public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { 1317 if (sub1 != null) { 1318 sub1.setDecimalFormatSymbols(newSymbols); 1319 } 1320 if (sub2 != null) { 1321 sub2.setDecimalFormatSymbols(newSymbols); 1322 } 1323 } 1324 } 1325