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