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 ohos.global.icu.text; 11 12 import java.text.FieldPosition; 13 import java.text.ParsePosition; 14 import java.util.List; 15 import java.util.Objects; 16 17 import ohos.global.icu.impl.PatternProps; 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 */ makeRules(String description, NFRuleSet owner, NFRule predecessor, RuleBasedNumberFormat ownersOwner, List<NFRule> returnList)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 */ NFRule(RuleBasedNumberFormat formatter, String ruleText)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 */ parseRuleDescriptor(String description)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 */ extractSubstitutions(NFRuleSet owner, String ruleText, NFRule predecessor)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 */ extractSubstitution(NFRuleSet owner, NFRule predecessor)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 */ setBaseValue(long newBaseValue)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 */ expectedExponent()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 */ indexOfAnyRulePrefix(String ruleText)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 @Override equals(Object that)615 public boolean equals(Object that) { 616 if (that instanceof NFRule) { 617 NFRule that2 = (NFRule)that; 618 619 return baseValue == that2.baseValue 620 && radix == that2.radix 621 && exponent == that2.exponent 622 && ruleText.equals(that2.ruleText) 623 && Objects.equals(sub1, that2.sub1) 624 && Objects.equals(sub2, that2.sub2); 625 } 626 return false; 627 } 628 629 @Override hashCode()630 public int hashCode() { 631 assert false : "hashCode not designed"; 632 return 42; 633 } 634 635 /** 636 * Returns a textual representation of the rule. This won't 637 * necessarily be the same as the description that this rule 638 * was created with, but it will produce the same result. 639 * @return A textual description of the rule 640 */ 641 @Override toString()642 public String toString() { 643 StringBuilder result = new StringBuilder(); 644 645 // start with the rule descriptor. Special-case the special rules 646 if (baseValue == NEGATIVE_NUMBER_RULE) { 647 result.append("-x: "); 648 } 649 else if (baseValue == IMPROPER_FRACTION_RULE) { 650 result.append('x').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: "); 651 } 652 else if (baseValue == PROPER_FRACTION_RULE) { 653 result.append('0').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: "); 654 } 655 else if (baseValue == MASTER_RULE) { 656 result.append('x').append(decimalPoint == 0 ? '.' : decimalPoint).append("0: "); 657 } 658 else if (baseValue == INFINITY_RULE) { 659 result.append("Inf: "); 660 } 661 else if (baseValue == NAN_RULE) { 662 result.append("NaN: "); 663 } 664 else { 665 // for a normal rule, write out its base value, and if the radix is 666 // something other than 10, write out the radix (with the preceding 667 // slash, of course). Then calculate the expected exponent and if 668 // if isn't the same as the actual exponent, write an appropriate 669 // number of > signs. Finally, terminate the whole thing with 670 // a colon. 671 result.append(String.valueOf(baseValue)); 672 if (radix != 10) { 673 result.append('/').append(radix); 674 } 675 int numCarets = expectedExponent() - exponent; 676 for (int i = 0; i < numCarets; i++) 677 result.append('>'); 678 result.append(": "); 679 } 680 681 // if the rule text begins with a space, write an apostrophe 682 // (whitespace after the rule descriptor is ignored; the 683 // apostrophe is used to make the whitespace significant) 684 if (ruleText.startsWith(" ") && (sub1 == null || sub1.getPos() != 0)) { 685 result.append('\''); 686 } 687 688 // now, write the rule's rule text, inserting appropriate 689 // substitution tokens in the appropriate places 690 StringBuilder ruleTextCopy = new StringBuilder(ruleText); 691 if (sub2 != null) { 692 ruleTextCopy.insert(sub2.getPos(), sub2.toString()); 693 } 694 if (sub1 != null) { 695 ruleTextCopy.insert(sub1.getPos(), sub1.toString()); 696 } 697 result.append(ruleTextCopy.toString()); 698 699 // and finally, top the whole thing off with a semicolon and 700 // return the result 701 result.append(';'); 702 return result.toString(); 703 } 704 705 //----------------------------------------------------------------------- 706 // simple accessors 707 //----------------------------------------------------------------------- 708 709 /** 710 * Returns the rule's base value 711 * @return The rule's base value 712 */ getDecimalPoint()713 public final char getDecimalPoint() { 714 return decimalPoint; 715 } 716 717 /** 718 * Returns the rule's base value 719 * @return The rule's base value 720 */ getBaseValue()721 public final long getBaseValue() { 722 return baseValue; 723 } 724 725 /** 726 * Returns the rule's divisor (the value that cotrols the behavior 727 * of its substitutions) 728 * @return The rule's divisor 729 */ getDivisor()730 public long getDivisor() { 731 return power(radix, exponent); 732 } 733 734 //----------------------------------------------------------------------- 735 // formatting 736 //----------------------------------------------------------------------- 737 738 /** 739 * Formats the number, and inserts the resulting text into 740 * toInsertInto. 741 * @param number The number being formatted 742 * @param toInsertInto The string where the resultant text should 743 * be inserted 744 * @param pos The position in toInsertInto where the resultant text 745 * should be inserted 746 */ doFormat(long number, StringBuilder toInsertInto, int pos, int recursionCount)747 public void doFormat(long number, StringBuilder toInsertInto, int pos, int recursionCount) { 748 // first, insert the rule's rule text into toInsertInto at the 749 // specified position, then insert the results of the substitutions 750 // into the right places in toInsertInto (notice we do the 751 // substitutions in reverse order so that the offsets don't get 752 // messed up) 753 int pluralRuleStart = ruleText.length(); 754 int lengthOffset = 0; 755 if (rulePatternFormat == null) { 756 toInsertInto.insert(pos, ruleText); 757 } 758 else { 759 pluralRuleStart = ruleText.indexOf("$("); 760 int pluralRuleEnd = ruleText.indexOf(")$", pluralRuleStart); 761 int initialLength = toInsertInto.length(); 762 if (pluralRuleEnd < ruleText.length() - 1) { 763 toInsertInto.insert(pos, ruleText.substring(pluralRuleEnd + 2)); 764 } 765 toInsertInto.insert(pos, rulePatternFormat.format(number / power(radix, exponent))); 766 if (pluralRuleStart > 0) { 767 toInsertInto.insert(pos, ruleText.substring(0, pluralRuleStart)); 768 } 769 lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength); 770 } 771 if (sub2 != null) { 772 sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 773 } 774 if (sub1 != null) { 775 sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 776 } 777 } 778 779 /** 780 * Formats the number, and inserts the resulting text into 781 * toInsertInto. 782 * @param number The number being formatted 783 * @param toInsertInto The string where the resultant text should 784 * be inserted 785 * @param pos The position in toInsertInto where the resultant text 786 * should be inserted 787 */ doFormat(double number, StringBuilder toInsertInto, int pos, int recursionCount)788 public void doFormat(double number, StringBuilder toInsertInto, int pos, int recursionCount) { 789 // first, insert the rule's rule text into toInsertInto at the 790 // specified position, then insert the results of the substitutions 791 // into the right places in toInsertInto 792 // [again, we have two copies of this routine that do the same thing 793 // so that we don't sacrifice precision in a long by casting it 794 // to a double] 795 int pluralRuleStart = ruleText.length(); 796 int lengthOffset = 0; 797 if (rulePatternFormat == null) { 798 toInsertInto.insert(pos, ruleText); 799 } 800 else { 801 pluralRuleStart = ruleText.indexOf("$("); 802 int pluralRuleEnd = ruleText.indexOf(")$", pluralRuleStart); 803 int initialLength = toInsertInto.length(); 804 if (pluralRuleEnd < ruleText.length() - 1) { 805 toInsertInto.insert(pos, ruleText.substring(pluralRuleEnd + 2)); 806 } 807 double pluralVal = number; 808 if (0 <= pluralVal && pluralVal < 1) { 809 // We're in a fractional rule, and we have to match the NumeratorSubstitution behavior. 810 // 2.3 can become 0.2999999999999998 for the fraction due to rounding errors. 811 pluralVal = Math.round(pluralVal * power(radix, exponent)); 812 } 813 else { 814 pluralVal = pluralVal / power(radix, exponent); 815 } 816 toInsertInto.insert(pos, rulePatternFormat.format((long)(pluralVal))); 817 if (pluralRuleStart > 0) { 818 toInsertInto.insert(pos, ruleText.substring(0, pluralRuleStart)); 819 } 820 lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength); 821 } 822 if (sub2 != null) { 823 sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 824 } 825 if (sub1 != null) { 826 sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); 827 } 828 } 829 830 /** 831 * This is an equivalent to Math.pow that accurately works on 64-bit numbers 832 * @param base The base 833 * @param exponent The exponent 834 * @return radix ** exponent 835 * @see Math#pow(double, double) 836 */ power(long base, short exponent)837 static long power(long base, short exponent) { 838 if (exponent < 0) { 839 throw new IllegalArgumentException("Exponent can not be negative"); 840 } 841 if (base < 0) { 842 throw new IllegalArgumentException("Base can not be negative"); 843 } 844 long result = 1; 845 while (exponent > 0) { 846 if ((exponent & 1) == 1) { 847 result *= base; 848 } 849 base *= base; 850 exponent >>= 1; 851 } 852 return result; 853 } 854 855 /** 856 * Used by the owning rule set to determine whether to invoke the 857 * rollback rule (i.e., whether this rule or the one that precedes 858 * it in the rule set's list should be used to format the number) 859 * @param number The number being formatted 860 * @return True if the rule set should use the rule that precedes 861 * this one in its list; false if it should use this rule 862 */ shouldRollBack(long number)863 public boolean shouldRollBack(long number) { 864 // we roll back if the rule contains a modulus substitution, 865 // the number being formatted is an even multiple of the rule's 866 // divisor, and the rule's base value is NOT an even multiple 867 // of its divisor 868 // In other words, if the original description had 869 // 100: << hundred[ >>]; 870 // that expands into 871 // 100: << hundred; 872 // 101: << hundred >>; 873 // internally. But when we're formatting 200, if we use the rule 874 // at 101, which would normally apply, we get "two hundred zero". 875 // To prevent this, we roll back and use the rule at 100 instead. 876 // This is the logic that makes this happen: the rule at 101 has 877 // a modulus substitution, its base value isn't an even multiple 878 // of 100, and the value we're trying to format _is_ an even 879 // multiple of 100. This is called the "rollback rule." 880 if (!((sub1 != null && sub1.isModulusSubstitution()) || (sub2 != null && sub2.isModulusSubstitution()))) { 881 return false; 882 } 883 long divisor = power(radix, exponent); 884 return (number % divisor) == 0 && (baseValue % divisor) != 0; 885 } 886 887 //----------------------------------------------------------------------- 888 // parsing 889 //----------------------------------------------------------------------- 890 891 /** 892 * Attempts to parse the string with this rule. 893 * @param text The string being parsed 894 * @param parsePosition On entry, the value is ignored and assumed to 895 * be 0. On exit, this has been updated with the position of the first 896 * character not consumed by matching the text against this rule 897 * (if this rule doesn't match the text at all, the parse position 898 * if left unchanged (presumably at 0) and the function returns 899 * new Long(0)). 900 * @param isFractionRule True if this rule is contained within a 901 * fraction rule set. This is only used if the rule has no 902 * substitutions. 903 * @return If this rule matched the text, this is the rule's base value 904 * combined appropriately with the results of parsing the substitutions. 905 * If nothing matched, this is new Long(0) and the parse position is 906 * left unchanged. The result will be an instance of Long if the 907 * result is an integer and Double otherwise. The result is never null. 908 */ doParse(String text, ParsePosition parsePosition, boolean isFractionRule, double upperBound, int nonNumericalExecutedRuleMask)909 public Number doParse(String text, ParsePosition parsePosition, boolean isFractionRule, 910 double upperBound, int nonNumericalExecutedRuleMask) { 911 912 // internally we operate on a copy of the string being parsed 913 // (because we're going to change it) and use our own ParsePosition 914 ParsePosition pp = new ParsePosition(0); 915 916 // check to see whether the text before the first substitution 917 // matches the text at the beginning of the string being 918 // parsed. If it does, strip that off the front of workText; 919 // otherwise, dump out with a mismatch 920 int sub1Pos = sub1 != null ? sub1.getPos() : ruleText.length(); 921 int sub2Pos = sub2 != null ? sub2.getPos() : ruleText.length(); 922 String workText = stripPrefix(text, ruleText.substring(0, sub1Pos), pp); 923 int prefixLength = text.length() - workText.length(); 924 925 if (pp.getIndex() == 0 && sub1Pos != 0) { 926 // commented out because ParsePosition doesn't have error index in 1.1.x 927 // parsePosition.setErrorIndex(pp.getErrorIndex()); 928 return ZERO; 929 } 930 if (baseValue == INFINITY_RULE) { 931 // If you match this, don't try to perform any calculations on it. 932 parsePosition.setIndex(pp.getIndex()); 933 return Double.POSITIVE_INFINITY; 934 } 935 if (baseValue == NAN_RULE) { 936 // If you match this, don't try to perform any calculations on it. 937 parsePosition.setIndex(pp.getIndex()); 938 return Double.NaN; 939 } 940 941 // this is the fun part. The basic guts of the rule-matching 942 // logic is matchToDelimiter(), which is called twice. The first 943 // time it searches the input string for the rule text BETWEEN 944 // the substitutions and tries to match the intervening text 945 // in the input string with the first substitution. If that 946 // succeeds, it then calls it again, this time to look for the 947 // rule text after the second substitution and to match the 948 // intervening input text against the second substitution. 949 // 950 // For example, say we have a rule that looks like this: 951 // first << middle >> last; 952 // and input text that looks like this: 953 // first one middle two last 954 // First we use stripPrefix() to match "first " in both places and 955 // strip it off the front, leaving 956 // one middle two last 957 // Then we use matchToDelimiter() to match " middle " and try to 958 // match "one" against a substitution. If it's successful, we now 959 // have 960 // two last 961 // We use matchToDelimiter() a second time to match " last" and 962 // try to match "two" against a substitution. If "two" matches 963 // the substitution, we have a successful parse. 964 // 965 // Since it's possible in many cases to find multiple instances 966 // of each of these pieces of rule text in the input string, 967 // we need to try all the possible combinations of these 968 // locations. This prevents us from prematurely declaring a mismatch, 969 // and makes sure we match as much input text as we can. 970 int highWaterMark = 0; 971 double result = 0; 972 int start = 0; 973 double tempBaseValue = Math.max(0, baseValue); 974 975 do { 976 // our partial parse result starts out as this rule's base 977 // value. If it finds a successful match, matchToDelimiter() 978 // will compose this in some way with what it gets back from 979 // the substitution, giving us a new partial parse result 980 pp.setIndex(0); 981 double partialResult = matchToDelimiter(workText, start, tempBaseValue, 982 ruleText.substring(sub1Pos, sub2Pos), rulePatternFormat, 983 pp, sub1, upperBound, nonNumericalExecutedRuleMask).doubleValue(); 984 985 // if we got a successful match (or were trying to match a 986 // null substitution), pp is now pointing at the first unmatched 987 // character. Take note of that, and try matchToDelimiter() 988 // on the input text again 989 if (pp.getIndex() != 0 || sub1 == null) { 990 start = pp.getIndex(); 991 992 String workText2 = workText.substring(pp.getIndex()); 993 ParsePosition pp2 = new ParsePosition(0); 994 995 // the second matchToDelimiter() will compose our previous 996 // partial result with whatever it gets back from its 997 // substitution if there's a successful match, giving us 998 // a real result 999 partialResult = matchToDelimiter(workText2, 0, partialResult, 1000 ruleText.substring(sub2Pos), rulePatternFormat, pp2, sub2, 1001 upperBound, nonNumericalExecutedRuleMask).doubleValue(); 1002 1003 // if we got a successful match on this second 1004 // matchToDelimiter() call, update the high-water mark 1005 // and result (if necessary) 1006 if (pp2.getIndex() != 0 || sub2 == null) { 1007 if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) { 1008 highWaterMark = prefixLength + pp.getIndex() + pp2.getIndex(); 1009 result = partialResult; 1010 } 1011 } 1012 // commented out because ParsePosition doesn't have error index in 1.1.x 1013 // else { 1014 // int temp = pp2.getErrorIndex() + sub1.getPos() + pp.getIndex(); 1015 // if (temp> parsePosition.getErrorIndex()) { 1016 // parsePosition.setErrorIndex(temp); 1017 // } 1018 // } 1019 } 1020 // commented out because ParsePosition doesn't have error index in 1.1.x 1021 // else { 1022 // int temp = sub1.getPos() + pp.getErrorIndex(); 1023 // if (temp > parsePosition.getErrorIndex()) { 1024 // parsePosition.setErrorIndex(temp); 1025 // } 1026 // } 1027 // keep trying to match things until the outer matchToDelimiter() 1028 // call fails to make a match (each time, it picks up where it 1029 // left off the previous time) 1030 } 1031 while (sub1Pos != sub2Pos && pp.getIndex() > 0 && pp.getIndex() 1032 < workText.length() && pp.getIndex() != start); 1033 1034 // update the caller's ParsePosition with our high-water mark 1035 // (i.e., it now points at the first character this function 1036 // didn't match-- the ParsePosition is therefore unchanged if 1037 // we didn't match anything) 1038 parsePosition.setIndex(highWaterMark); 1039 // commented out because ParsePosition doesn't have error index in 1.1.x 1040 // if (highWaterMark > 0) { 1041 // parsePosition.setErrorIndex(0); 1042 // } 1043 1044 // this is a hack for one unusual condition: Normally, whether this 1045 // rule belong to a fraction rule set or not is handled by its 1046 // substitutions. But if that rule HAS NO substitutions, then 1047 // we have to account for it here. By definition, if the matching 1048 // rule in a fraction rule set has no substitutions, its numerator 1049 // is 1, and so the result is the reciprocal of its base value. 1050 if (isFractionRule && highWaterMark > 0 && sub1 == null) { 1051 result = 1 / result; 1052 } 1053 1054 // return the result as a Long if possible, or as a Double 1055 if (result == (long)result) { 1056 return Long.valueOf((long)result); 1057 } else { 1058 return new Double(result); 1059 } 1060 } 1061 1062 /** 1063 * This function is used by parse() to match the text being parsed 1064 * against a possible prefix string. This function 1065 * matches characters from the beginning of the string being parsed 1066 * to characters from the prospective prefix. If they match, pp is 1067 * updated to the first character not matched, and the result is 1068 * the unparsed part of the string. If they don't match, the whole 1069 * string is returned, and pp is left unchanged. 1070 * @param text The string being parsed 1071 * @param prefix The text to match against 1072 * @param pp On entry, ignored and assumed to be 0. On exit, points 1073 * to the first unmatched character (assuming the whole prefix matched), 1074 * or is unchanged (if the whole prefix didn't match). 1075 * @return If things match, this is the unparsed part of "text"; 1076 * if they didn't match, this is "text". 1077 */ stripPrefix(String text, String prefix, ParsePosition pp)1078 private String stripPrefix(String text, String prefix, ParsePosition pp) { 1079 // if the prefix text is empty, dump out without doing anything 1080 if (prefix.length() == 0) { 1081 return text; 1082 } else { 1083 // otherwise, use prefixLength() to match the beginning of 1084 // "text" against "prefix". This function returns the 1085 // number of characters from "text" that matched (or 0 if 1086 // we didn't match the whole prefix) 1087 int pfl = prefixLength(text, prefix); 1088 if (pfl != 0) { 1089 // if we got a successful match, update the parse position 1090 // and strip the prefix off of "text" 1091 pp.setIndex(pp.getIndex() + pfl); 1092 return text.substring(pfl); 1093 1094 // if we didn't get a successful match, leave everything alone 1095 } else { 1096 return text; 1097 } 1098 } 1099 } 1100 1101 /** 1102 * Used by parse() to match a substitution and any following text. 1103 * "text" is searched for instances of "delimiter". For each instance 1104 * of delimiter, the intervening text is tested to see whether it 1105 * matches the substitution. The longest match wins. 1106 * @param text The string being parsed 1107 * @param startPos The position in "text" where we should start looking 1108 * for "delimiter". 1109 * @param baseVal A partial parse result (often the rule's base value), 1110 * which is combined with the result from matching the substitution 1111 * @param delimiter The string to search "text" for. 1112 * @param pp Ignored and presumed to be 0 on entry. If there's a match, 1113 * on exit this will point to the first unmatched character. 1114 * @param sub If we find "delimiter" in "text", this substitution is used 1115 * to match the text between the beginning of the string and the 1116 * position of "delimiter." (If "delimiter" is the empty string, then 1117 * this function just matches against this substitution and updates 1118 * everything accordingly.) 1119 * @param upperBound When matching the substitution, it will only 1120 * consider rules with base values lower than this value. 1121 * @return If there's a match, this is the result of composing 1122 * baseValue with the result of matching the substitution. Otherwise, 1123 * this is new Long(0). It's never null. If the result is an integer, 1124 * this will be an instance of Long; otherwise, it's an instance of 1125 * Double. 1126 */ matchToDelimiter(String text, int startPos, double baseVal, String delimiter, PluralFormat pluralFormatDelimiter, ParsePosition pp, NFSubstitution sub, double upperBound, int nonNumericalExecutedRuleMask)1127 private Number matchToDelimiter(String text, int startPos, double baseVal, 1128 String delimiter, PluralFormat pluralFormatDelimiter, ParsePosition pp, NFSubstitution sub, 1129 double upperBound, int nonNumericalExecutedRuleMask) { 1130 // if "delimiter" contains real (i.e., non-ignorable) text, search 1131 // it for "delimiter" beginning at "start". If that succeeds, then 1132 // use "sub"'s doParse() method to match the text before the 1133 // instance of "delimiter" we just found. 1134 if (!allIgnorable(delimiter)) { 1135 ParsePosition tempPP = new ParsePosition(0); 1136 Number tempResult; 1137 1138 // use findText() to search for "delimiter". It returns a two- 1139 // element array: element 0 is the position of the match, and 1140 // element 1 is the number of characters that matched 1141 // "delimiter". 1142 int[] temp = findText(text, delimiter, pluralFormatDelimiter, startPos); 1143 int dPos = temp[0]; 1144 int dLen = temp[1]; 1145 1146 // if findText() succeeded, isolate the text preceding the 1147 // match, and use "sub" to match that text 1148 while (dPos >= 0) { 1149 String subText = text.substring(0, dPos); 1150 if (subText.length() > 0) { 1151 tempResult = sub.doParse(subText, tempPP, baseVal, upperBound, 1152 formatter.lenientParseEnabled(), nonNumericalExecutedRuleMask); 1153 1154 // if the substitution could match all the text up to 1155 // where we found "delimiter", then this function has 1156 // a successful match. Bump the caller's parse position 1157 // to point to the first character after the text 1158 // that matches "delimiter", and return the result 1159 // we got from parsing the substitution. 1160 if (tempPP.getIndex() == dPos) { 1161 pp.setIndex(dPos + dLen); 1162 return tempResult; 1163 } 1164 // commented out because ParsePosition doesn't have error index in 1.1.x 1165 // else { 1166 // if (tempPP.getErrorIndex() > 0) { 1167 // pp.setErrorIndex(tempPP.getErrorIndex()); 1168 // } else { 1169 // pp.setErrorIndex(tempPP.getIndex()); 1170 // } 1171 // } 1172 } 1173 1174 // if we didn't match the substitution, search for another 1175 // copy of "delimiter" in "text" and repeat the loop if 1176 // we find it 1177 tempPP.setIndex(0); 1178 temp = findText(text, delimiter, pluralFormatDelimiter, dPos + dLen); 1179 dPos = temp[0]; 1180 dLen = temp[1]; 1181 } 1182 // if we make it here, this was an unsuccessful match, and we 1183 // leave pp unchanged and return 0 1184 pp.setIndex(0); 1185 return ZERO; 1186 1187 // if "delimiter" is empty, or consists only of ignorable characters 1188 // (i.e., is semantically empty), thwe we obviously can't search 1189 // for "delimiter". Instead, just use "sub" to parse as much of 1190 // "text" as possible. 1191 } 1192 else if (sub == null) { 1193 return baseVal; 1194 } 1195 else { 1196 ParsePosition tempPP = new ParsePosition(0); 1197 Number result = ZERO; 1198 // try to match the whole string against the substitution 1199 Number tempResult = sub.doParse(text, tempPP, baseVal, upperBound, 1200 formatter.lenientParseEnabled(), nonNumericalExecutedRuleMask); 1201 if (tempPP.getIndex() != 0) { 1202 // if there's a successful match (or it's a null 1203 // substitution), update pp to point to the first 1204 // character we didn't match, and pass the result from 1205 // sub.doParse() on through to the caller 1206 pp.setIndex(tempPP.getIndex()); 1207 if (tempResult != null) { 1208 result = tempResult; 1209 } 1210 } 1211 // commented out because ParsePosition doesn't have error index in 1.1.x 1212 // else { 1213 // pp.setErrorIndex(tempPP.getErrorIndex()); 1214 // } 1215 1216 // and if we get to here, then nothing matched, so we return 1217 // 0 and leave pp alone 1218 return result; 1219 } 1220 } 1221 1222 /** 1223 * Used by stripPrefix() to match characters. If lenient parse mode 1224 * is off, this just calls startsWith(). If lenient parse mode is on, 1225 * this function uses CollationElementIterators to match characters in 1226 * the strings (only primary-order differences are significant in 1227 * determining whether there's a match). 1228 * @param str The string being tested 1229 * @param prefix The text we're hoping to see at the beginning 1230 * of "str" 1231 * @return If "prefix" is found at the beginning of "str", this 1232 * is the number of characters in "str" that were matched (this 1233 * isn't necessarily the same as the length of "prefix" when matching 1234 * text with a collator). If there's no match, this is 0. 1235 */ prefixLength(String str, String prefix)1236 private int prefixLength(String str, String prefix) { 1237 // if we're looking for an empty prefix, it obviously matches 1238 // zero characters. Just go ahead and return 0. 1239 if (prefix.length() == 0) { 1240 return 0; 1241 } 1242 1243 RbnfLenientScanner scanner = formatter.getLenientScanner(); 1244 if (scanner != null) { 1245 return scanner.prefixLength(str, prefix); 1246 } 1247 1248 // If lenient parsing is turned off, forget all that crap above. 1249 // Just use String.startsWith() and be done with it. 1250 if (str.startsWith(prefix)) { 1251 return prefix.length(); 1252 } 1253 return 0; 1254 } 1255 1256 /** 1257 * Searches a string for another string. If lenient parsing is off, 1258 * this just calls indexOf(). If lenient parsing is on, this function 1259 * uses CollationElementIterator to match characters, and only 1260 * primary-order differences are significant in determining whether 1261 * there's a match. 1262 * @param str The string to search 1263 * @param key The string to search "str" for 1264 * @param startingAt The index into "str" where the search is to 1265 * begin 1266 * @return A two-element array of ints. Element 0 is the position 1267 * of the match, or -1 if there was no match. Element 1 is the 1268 * number of characters in "str" that matched (which isn't necessarily 1269 * the same as the length of "key") 1270 */ findText(String str, String key, PluralFormat pluralFormatKey, int startingAt)1271 private int[] findText(String str, String key, PluralFormat pluralFormatKey, int startingAt) { 1272 RbnfLenientScanner scanner = formatter.getLenientScanner(); 1273 if (pluralFormatKey != null) { 1274 FieldPosition position = new FieldPosition(NumberFormat.INTEGER_FIELD); 1275 position.setBeginIndex(startingAt); 1276 pluralFormatKey.parseType(str, scanner, position); 1277 int start = position.getBeginIndex(); 1278 if (start >= 0) { 1279 int pluralRuleStart = ruleText.indexOf("$("); 1280 int pluralRuleSuffix = ruleText.indexOf(")$", pluralRuleStart) + 2; 1281 int matchLen = position.getEndIndex() - start; 1282 String prefix = ruleText.substring(0, pluralRuleStart); 1283 String suffix = ruleText.substring(pluralRuleSuffix); 1284 if (str.regionMatches(start - prefix.length(), prefix, 0, prefix.length()) 1285 && str.regionMatches(start + matchLen, suffix, 0, suffix.length())) 1286 { 1287 return new int[]{start - prefix.length(), matchLen + prefix.length() + suffix.length()}; 1288 } 1289 } 1290 return new int[]{-1, 0}; 1291 } 1292 1293 if (scanner != null) { 1294 // if lenient parsing is turned ON, we've got some work 1295 // ahead of us 1296 return scanner.findText(str, key, startingAt); 1297 } 1298 // if lenient parsing is turned off, this is easy. Just call 1299 // String.indexOf() and we're done 1300 return new int[]{str.indexOf(key, startingAt), key.length()}; 1301 } 1302 1303 /** 1304 * Checks to see whether a string consists entirely of ignorable 1305 * characters. 1306 * @param str The string to test. 1307 * @return true if the string is empty of consists entirely of 1308 * characters that the number formatter's collator says are 1309 * ignorable at the primary-order level. false otherwise. 1310 */ allIgnorable(String str)1311 private boolean allIgnorable(String str) { 1312 // if the string is empty, we can just return true 1313 if (str == null || str.length() == 0) { 1314 return true; 1315 } 1316 RbnfLenientScanner scanner = formatter.getLenientScanner(); 1317 return scanner != null && scanner.allIgnorable(str); 1318 } 1319 setDecimalFormatSymbols(DecimalFormatSymbols newSymbols)1320 public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { 1321 if (sub1 != null) { 1322 sub1.setDecimalFormatSymbols(newSymbols); 1323 } 1324 if (sub2 != null) { 1325 sub2.setDecimalFormatSymbols(newSymbols); 1326 } 1327 } 1328 } 1329