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