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