• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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