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