001: /*
002: *******************************************************************************
003: * Copyright (C) 1996-2004, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007: package com.ibm.icu.text;
008:
009: import com.ibm.icu.impl.UCharacterProperty;
010: import com.ibm.icu.impl.Utility;
011:
012: import java.text.*;
013: import java.util.Vector;
014:
015: /**
016: * A collection of rules used by a RuleBasedNumberFormat to format and
017: * parse numbers. It is the responsibility of a RuleSet to select an
018: * appropriate rule for formatting a particular number and dispatch
019: * control to it, and to arbitrate between different rules when parsing
020: * a number.
021: */
022:
023: final class NFRuleSet {
024: //-----------------------------------------------------------------------
025: // constants
026: //-----------------------------------------------------------------------
027:
028: /**
029: * Puts a copyright in the .class file
030: */
031: private static final String copyrightNotice = "Copyright \u00a91997-1998 IBM Corp. All rights reserved.";
032:
033: //-----------------------------------------------------------------------
034: // data members
035: //-----------------------------------------------------------------------
036:
037: /**
038: * The rule set's name
039: */
040: private String name;
041:
042: /**
043: * The rule set's regular rules
044: */
045: private NFRule[] rules;
046:
047: /**
048: * The rule set's negative-number rule
049: */
050: private NFRule negativeNumberRule = null;
051:
052: /**
053: * The rule set's fraction rules: element 0 is the proper fraction
054: * (0.x) rule, element 1 is the improper fraction (x.x) rule, and
055: * element 2 is the master (x.0) rule.
056: */
057: private NFRule[] fractionRules = new NFRule[3];
058:
059: /**
060: * True if the rule set is a fraction rule set. A fraction rule set
061: * is a rule set that is used to format the fractional part of a
062: * number. It is called from a >> substitution in another rule set's
063: * fraction rule, and is only called upon to format values between
064: * 0 and 1. A fraction rule set has different rule-selection
065: * behavior than a regular rule set.
066: */
067: private boolean isFractionRuleSet = false;
068:
069: /**
070: * Used to limit recursion for bad rule sets.
071: */
072: private int recursionCount = 0;
073:
074: /**
075: * Limit of recursion.
076: */
077: private static final int RECURSION_LIMIT = 50;
078:
079: //-----------------------------------------------------------------------
080: // construction
081: //-----------------------------------------------------------------------
082:
083: /*
084: * Constructs a rule set.
085: * @param descriptions An array of Strings representing rule set
086: * descriptions. On exit, this rule set's entry in the array will
087: * have been stripped of its rule set name and any trailing whitespace.
088: * @param index The index into "descriptions" of the description
089: * for the rule to be constructed
090: */
091: public NFRuleSet(String[] descriptions, int index)
092: throws IllegalArgumentException {
093: String description = descriptions[index];
094:
095: if (description.length() == 0) {
096: throw new IllegalArgumentException(
097: "Empty rule set description");
098: }
099:
100: // if the description begins with a rule set name (the rule set
101: // name can be omitted in formatter descriptions that consist
102: // of only one rule set), copy it out into our "name" member
103: // and delete it from the description
104: if (description.charAt(0) == '%') {
105: int pos = description.indexOf(':');
106: if (pos == -1) {
107: throw new IllegalArgumentException(
108: "Rule set name doesn't end in colon");
109: } else {
110: name = description.substring(0, pos);
111: while (pos < description.length()
112: && UCharacterProperty
113: .isRuleWhiteSpace(description
114: .charAt(++pos))) {
115: }
116: description = description.substring(pos);
117: descriptions[index] = description;
118: }
119:
120: // if the description doesn't begin with a rule set name, its
121: // name is "%default"
122: } else {
123: name = "%default";
124: }
125:
126: if (description.length() == 0) {
127: throw new IllegalArgumentException(
128: "Empty rule set description");
129: }
130:
131: // all of the other members of NFRuleSet are initialized
132: // by parseRules()
133: }
134:
135: /**
136: * Construct the subordinate data structures used by this object.
137: * This function is called by the RuleBasedNumberFormat constructor
138: * after all the rule sets have been created to actually parse
139: * the description and build rules from it. Since any rule set
140: * can refer to any other rule set, we have to have created all of
141: * them before we can create anything else.
142: * @param description The textual description of this rule set
143: * @param owner The formatter that owns this rule set
144: */
145: public void parseRules(String description,
146: RuleBasedNumberFormat owner) {
147: // start by creating a Vector whose elements are Strings containing
148: // the descriptions of the rules (one rule per element). The rules
149: // are separated by semicolons (there's no escape facility: ALL
150: // semicolons are rule delimiters)
151: Vector ruleDescriptions = new Vector();
152:
153: int oldP = 0;
154: int p = description.indexOf(';');
155: while (oldP != -1) {
156: if (p != -1) {
157: ruleDescriptions.addElement(description.substring(oldP,
158: p));
159: oldP = p + 1;
160: } else {
161: if (oldP < description.length()) {
162: ruleDescriptions.addElement(description
163: .substring(oldP));
164: }
165: oldP = p;
166: }
167: p = description.indexOf(';', p + 1);
168: }
169:
170: // now go back through and build a vector of the rules themselves
171: // (the number of elements in the description list isn't necessarily
172: // the number of rules-- some descriptions may expend into two rules)
173: Vector tempRules = new Vector();
174:
175: // we keep track of the rule before the one we're currently working
176: // on solely to support >>> substitutions
177: NFRule predecessor = null;
178: for (int i = 0; i < ruleDescriptions.size(); i++) {
179: // makeRules (a factory method on NFRule) will return either
180: // a single rule or an array of rules. Either way, add them
181: // to our rule vector
182: Object temp = NFRule.makeRules((String) ruleDescriptions
183: .elementAt(i), this , predecessor, owner);
184:
185: if (temp instanceof NFRule) {
186: tempRules.addElement(temp);
187: predecessor = (NFRule) temp;
188: } else if (temp instanceof NFRule[]) {
189: NFRule[] rulesToAdd = (NFRule[]) temp;
190:
191: for (int j = 0; j < rulesToAdd.length; j++) {
192: tempRules.addElement(rulesToAdd[j]);
193: predecessor = rulesToAdd[j];
194: }
195: }
196: }
197: // now we can bag the description list
198: ruleDescriptions = null;
199:
200: // for rules that didn't specify a base value, their base values
201: // were initialized to 0. Make another pass through the list and
202: // set all those rules' base values. We also remove any special
203: // rules from the list and put them into their own member variables
204: long defaultBaseValue = 0;
205:
206: // (this isn't a for loop because we might be deleting items from
207: // the vector-- we want to make sure we only increment i when
208: // we _didn't_ delete aything from the vector)
209: int i = 0;
210: while (i < tempRules.size()) {
211: NFRule rule = (NFRule) tempRules.elementAt(i);
212:
213: switch ((int) rule.getBaseValue()) {
214: // if the rule's base value is 0, fill in a default
215: // base value (this will be 1 plus the preceding
216: // rule's base value for regular rule sets, and the
217: // same as the preceding rule's base value in fraction
218: // rule sets)
219: case 0:
220: rule.setBaseValue(defaultBaseValue);
221: if (!isFractionRuleSet) {
222: ++defaultBaseValue;
223: }
224: ++i;
225: break;
226:
227: // if it's the negative-number rule, copy it into its own
228: // data member and delete it from the list
229: case NFRule.NEGATIVE_NUMBER_RULE:
230: negativeNumberRule = rule;
231: tempRules.removeElementAt(i);
232: break;
233:
234: // if it's the improper fraction rule, copy it into the
235: // correct element of fractionRules
236: case NFRule.IMPROPER_FRACTION_RULE:
237: fractionRules[0] = rule;
238: tempRules.removeElementAt(i);
239: break;
240:
241: // if it's the proper fraction rule, copy it into the
242: // correct element of fractionRules
243: case NFRule.PROPER_FRACTION_RULE:
244: fractionRules[1] = rule;
245: tempRules.removeElementAt(i);
246: break;
247:
248: // if it's the master rule, copy it into the
249: // correct element of fractionRules
250: case NFRule.MASTER_RULE:
251: fractionRules[2] = rule;
252: tempRules.removeElementAt(i);
253: break;
254:
255: // if it's a regular rule that already knows its base value,
256: // check to make sure the rules are in order, and update
257: // the default base value for the next rule
258: default:
259: if (rule.getBaseValue() < defaultBaseValue) {
260: throw new IllegalArgumentException(
261: "Rules are not in order, base: "
262: + rule.getBaseValue() + " < "
263: + defaultBaseValue);
264: }
265: defaultBaseValue = rule.getBaseValue();
266: if (!isFractionRuleSet) {
267: ++defaultBaseValue;
268: }
269: ++i;
270: break;
271: }
272: }
273:
274: // finally, we can copy the rules from the vector into a
275: // fixed-length array
276: rules = new NFRule[tempRules.size()];
277: tempRules.copyInto((Object[]) rules);
278: }
279:
280: /**
281: * Flags this rule set as a fraction rule set. This function is
282: * called during the construction process once we know this rule
283: * set is a fraction rule set. We don't know a rule set is a
284: * fraction rule set until we see it used somewhere. This function
285: * is not ad must not be called at any time other than during
286: * construction of a RuleBasedNumberFormat.
287: */
288: public void makeIntoFractionRuleSet() {
289: isFractionRuleSet = true;
290: }
291:
292: //-----------------------------------------------------------------------
293: // boilerplate
294: //-----------------------------------------------------------------------
295:
296: /**
297: * Compares two rule sets for equality.
298: * @param that The other rule set
299: * @return true if the two rule sets are functionally equivalent.
300: */
301: public boolean equals(Object that) {
302: // if different classes, they're not equal
303: if (!(that instanceof NFRuleSet)) {
304: return false;
305: } else {
306: // otherwise, compare the members one by one...
307: NFRuleSet that2 = (NFRuleSet) that;
308:
309: if (!name.equals(that2.name)
310: || !Utility.objectEquals(negativeNumberRule,
311: that2.negativeNumberRule)
312: || !Utility.objectEquals(fractionRules[0],
313: that2.fractionRules[0])
314: || !Utility.objectEquals(fractionRules[1],
315: that2.fractionRules[1])
316: || !Utility.objectEquals(fractionRules[2],
317: that2.fractionRules[2])
318: || rules.length != that2.rules.length
319: || isFractionRuleSet != that2.isFractionRuleSet) {
320:
321: return false;
322: }
323:
324: // ...then compare the rule lists...
325: for (int i = 0; i < rules.length; i++) {
326: if (!rules[i].equals(that2.rules[i])) {
327: return false;
328: }
329: }
330:
331: // ...and if we make it here, tney're equal
332: return true;
333: }
334: }
335:
336: /**
337: * Builds a textual representation of a rule set.
338: * @return A textual representation of a rule set. This won't
339: * necessarily be the same description that the rule set was
340: * constructed with, but it will produce the same results.
341: */
342: public String toString() {
343: StringBuffer result = new StringBuffer();
344:
345: // the rule set name goes first...
346: result.append(name + ":\n");
347:
348: // followed by the regular rules...
349: for (int i = 0; i < rules.length; i++) {
350: result.append(" " + rules[i].toString() + "\n");
351: }
352:
353: // followed by the special rules (if they exist)
354: if (negativeNumberRule != null) {
355: result
356: .append(" " + negativeNumberRule.toString()
357: + "\n");
358: }
359: if (fractionRules[0] != null) {
360: result.append(" " + fractionRules[0].toString() + "\n");
361: }
362: if (fractionRules[1] != null) {
363: result.append(" " + fractionRules[1].toString() + "\n");
364: }
365: if (fractionRules[2] != null) {
366: result.append(" " + fractionRules[2].toString() + "\n");
367: }
368:
369: return result.toString();
370: }
371:
372: //-----------------------------------------------------------------------
373: // simple accessors
374: //-----------------------------------------------------------------------
375:
376: /**
377: * Says whether this rule set is a fraction rule set.
378: * @return true if this rule is a fraction rule set; false if it isn't
379: */
380: public boolean isFractionSet() {
381: return isFractionRuleSet;
382: }
383:
384: /**
385: * Returns the rule set's name
386: * @return The rule set's name
387: */
388: public String getName() {
389: return name;
390: }
391:
392: /**
393: * Return true if the rule set is public.
394: * @return true if the rule set is public
395: */
396: public boolean isPublic() {
397: return !name.startsWith("%%");
398: }
399:
400: //-----------------------------------------------------------------------
401: // formatting
402: //-----------------------------------------------------------------------
403:
404: /**
405: * Formats a long. Selects an appropriate rule and dispatches
406: * control to it.
407: * @param number The number being formatted
408: * @param toInsertInto The string where the result is to be placed
409: * @param pos The position in toInsertInto where the result of
410: * this operation is to be inserted
411: */
412: public void format(long number, StringBuffer toInsertInto, int pos) {
413: NFRule applicableRule = findNormalRule(number);
414:
415: if (++recursionCount >= RECURSION_LIMIT) {
416: recursionCount = 0;
417: throw new IllegalStateException(
418: "Recursion limit exceeded when applying ruleSet "
419: + name);
420: }
421: applicableRule.doFormat(number, toInsertInto, pos);
422: --recursionCount;
423: }
424:
425: /**
426: * Formats a double. Selects an appropriate rule and dispatches
427: * control to it.
428: * @param number The number being formatted
429: * @param toInsertInto The string where the result is to be placed
430: * @param pos The position in toInsertInto where the result of
431: * this operation is to be inserted
432: */
433: public void format(double number, StringBuffer toInsertInto, int pos) {
434: NFRule applicableRule = findRule(number);
435:
436: if (++recursionCount >= RECURSION_LIMIT) {
437: recursionCount = 0;
438: throw new IllegalStateException(
439: "Recursion limit exceeded when applying ruleSet "
440: + name);
441: }
442: applicableRule.doFormat(number, toInsertInto, pos);
443: --recursionCount;
444: }
445:
446: /**
447: * Selects an apropriate rule for formatting the number.
448: * @param number The number being formatted.
449: * @return The rule that should be used to format it
450: */
451: private NFRule findRule(double number) {
452: // if this is a fraction rule set, use findFractionRuleSetRule()
453: if (isFractionRuleSet) {
454: return findFractionRuleSetRule(number);
455: }
456:
457: // if the number is negative, return the negative number rule
458: // (if there isn't a negative-number rule, we pretend it's a
459: // positive number)
460: if (number < 0) {
461: if (negativeNumberRule != null) {
462: return negativeNumberRule;
463: } else {
464: number = -number;
465: }
466: }
467:
468: // if the number isn't an integer, we use one f the fraction rules...
469: if (number != Math.floor(number)) {
470: // if the number is between 0 and 1, return the proper
471: // fraction rule
472: if (number < 1 && fractionRules[1] != null) {
473: return fractionRules[1];
474: }
475:
476: // otherwise, return the improper fraction rule
477: else if (fractionRules[0] != null) {
478: return fractionRules[0];
479: }
480: }
481:
482: // if there's a master rule, use it to format the number
483: if (fractionRules[2] != null) {
484: return fractionRules[2];
485:
486: // and if we haven't yet returned a rule, use findNormalRule()
487: // to find the applicable rule
488: } else {
489: return findNormalRule((long) Math.round(number));
490: }
491: }
492:
493: /**
494: * If the value passed to findRule() is a positive integer, findRule()
495: * uses this function to select the appropriate rule. The result will
496: * generally be the rule with the highest base value less than or equal
497: * to the number. There is one exception to this: If that rule has
498: * two substitutions and a base value that is not an even multiple of
499: * its divisor, and the number itself IS an even multiple of the rule's
500: * divisor, then the result will be the rule that preceded the original
501: * result in the rule list. (This behavior is known as the "rollback
502: * rule", and is used to handle optional text: a rule with optional
503: * text is represented internally as two rules, and the rollback rule
504: * selects appropriate between them. This avoids things like "two
505: * hundred zero".)
506: * @param number The number being formatted
507: * @return The rule to use to format this number
508: */
509: private NFRule findNormalRule(long number) {
510: // if this is a fraction rule set, use findFractionRuleSetRule()
511: // to find the rule (we should only go into this clause if the
512: // value is 0)
513: if (isFractionRuleSet) {
514: return findFractionRuleSetRule(number);
515: }
516:
517: // if the number is negative, return the negative-number rule
518: // (if there isn't one, pretend the number is positive)
519: if (number < 0) {
520: if (negativeNumberRule != null) {
521: return negativeNumberRule;
522: } else {
523: number = -number;
524: }
525: }
526:
527: // we have to repeat the preceding two checks, even though we
528: // do them in findRule(), because the version of format() that
529: // takes a long bypasses findRule() and goes straight to this
530: // function. This function does skip the fraction rules since
531: // we know the value is an integer (it also skips the master
532: // rule, since it's considered a fraction rule. Skipping the
533: // master rule in this function is also how we avoid infinite
534: // recursion)
535:
536: // binary-search the rule list for the applicable rule
537: // (a rule is used for all values from its base value to
538: // the next rule's base value)
539: int lo = 0;
540: int hi = rules.length;
541: if (hi > 0) {
542: while (lo < hi) {
543: int mid = (lo + hi) / 2;
544: if (rules[mid].getBaseValue() == number) {
545: return rules[mid];
546: } else if (rules[mid].getBaseValue() > number) {
547: hi = mid;
548: } else {
549: lo = mid + 1;
550: }
551: }
552: if (hi == 0) { // bad rule set
553: throw new IllegalStateException("The rule set " + name
554: + " cannot format the value " + number);
555: }
556: NFRule result = rules[hi - 1];
557:
558: // use shouldRollBack() to see whether we need to invoke the
559: // rollback rule (see shouldRollBack()'s documentation for
560: // an explanation of the rollback rule). If we do, roll back
561: // one rule and return that one instead of the one we'd normally
562: // return
563: if (result.shouldRollBack(number)) {
564: if (hi == 1) { // bad rule set
565: throw new IllegalStateException("The rule set "
566: + name
567: + " cannot roll back from the rule '"
568: + result + "'");
569: }
570: result = rules[hi - 2];
571: }
572: return result;
573: }
574: // else use the master rule
575: return fractionRules[2];
576: }
577:
578: /**
579: * If this rule is a fraction rule set, this function is used by
580: * findRule() to select the most appropriate rule for formatting
581: * the number. Basically, the base value of each rule in the rule
582: * set is treated as the denominator of a fraction. Whichever
583: * denominator can produce the fraction closest in value to the
584: * number passed in is the result. If there's a tie, the earlier
585: * one in the list wins. (If there are two rules in a row with the
586: * same base value, the first one is used when the numerator of the
587: * fraction would be 1, and the second rule is used the rest of the
588: * time.
589: * @param number The number being formatted (which will always be
590: * a number between 0 and 1)
591: * @return The rule to use to format this number
592: */
593: private NFRule findFractionRuleSetRule(double number) {
594: // the obvious way to do this (multiply the value being formatted
595: // by each rule's base value until you get an integral result)
596: // doesn't work because of rounding error. This method is more
597: // accurate
598:
599: // find the least common multiple of the rules' base values
600: // and multiply this by the number being formatted. This is
601: // all the precision we need, and we can do all of the rest
602: // of the math using integer arithmetic
603: long leastCommonMultiple = rules[0].getBaseValue();
604: for (int i = 1; i < rules.length; i++) {
605: leastCommonMultiple = lcm(leastCommonMultiple, rules[i]
606: .getBaseValue());
607: }
608: long numerator = (long) (Math.round(number
609: * leastCommonMultiple));
610:
611: // for each rule, do the following...
612: long tempDifference;
613: long difference = Long.MAX_VALUE;
614: int winner = 0;
615: for (int i = 0; i < rules.length; i++) {
616: // "numerator" is the numerator of the fraction is the
617: // denominator is the LCD. The numerator if the the rule's
618: // base value is the denomiator is "numerator" times the
619: // base value divided bythe LCD. Here we check to see if
620: // that's an integer, and if not, how close it is to being
621: // an integer.
622: tempDifference = numerator * rules[i].getBaseValue()
623: % leastCommonMultiple;
624:
625: // normalize the result of the above calculation: we want
626: // the numerator's distance from the CLOSEST multiple
627: // of the LCD
628: if (leastCommonMultiple - tempDifference < tempDifference) {
629: tempDifference = leastCommonMultiple - tempDifference;
630: }
631:
632: // if this is as close as we've come, keep track of how close
633: // that is, and the line number of the rule that did it. If
634: // we've scored a direct hit, we don't have to look at any more
635: // rules
636: if (tempDifference < difference) {
637: difference = tempDifference;
638: winner = i;
639: if (difference == 0) {
640: break;
641: }
642: }
643: }
644:
645: // if we have two successive rules that both have the winning base
646: // value, then the first one (the one we found above) is used if
647: // the numerator of the fraction is 1 and the second one is used if
648: // the numerator of the fraction is anything else (this lets us
649: // do things like "one third"/"two thirds" without haveing to define
650: // a whole bunch of extra rule sets)
651: if (winner + 1 < rules.length
652: && rules[winner + 1].getBaseValue() == rules[winner]
653: .getBaseValue()) {
654: if (Math.round(number * rules[winner].getBaseValue()) < 1
655: || Math
656: .round(number
657: * rules[winner].getBaseValue()) >= 2) {
658: ++winner;
659: }
660: }
661:
662: // finally, return the winning rule
663: return rules[winner];
664: }
665:
666: /**
667: * Calculates the least common multiple of x and y.
668: */
669: private static long lcm(long x, long y) {
670: // binary gcd algorithm from Knuth, "The Art of Computer Programming,"
671: // vol. 2, 1st ed., pp. 298-299
672: long x1 = x;
673: long y1 = y;
674:
675: int p2 = 0;
676: while ((x1 & 1) == 0 && (y1 & 1) == 0) {
677: ++p2;
678: x1 >>= 1;
679: y1 >>= 1;
680: }
681:
682: long t;
683: if ((x1 & 1) == 1) {
684: t = -y1;
685: } else {
686: t = x1;
687: }
688:
689: while (t != 0) {
690: while ((t & 1) == 0) {
691: t >>= 1;
692: }
693: if (t > 0) {
694: x1 = t;
695: } else {
696: y1 = -t;
697: }
698: t = x1 - y1;
699: }
700: long gcd = x1 << p2;
701:
702: // x * y == gcd(x, y) * lcm(x, y)
703: return x / gcd * y;
704: }
705:
706: //-----------------------------------------------------------------------
707: // parsing
708: //-----------------------------------------------------------------------
709:
710: /**
711: * Parses a string. Matches the string to be parsed against each
712: * of its rules (with a base value less than upperBound) and returns
713: * the value produced by the rule that matched the most charcters
714: * in the source string.
715: * @param text The string to parse
716: * @param parsePosition The initial position is ignored and assumed
717: * to be 0. On exit, this object has been updated to point to the
718: * first character position this rule set didn't consume.
719: * @param upperBound Limits the rules that can be allowed to match.
720: * Only rules whose base values are strictly less than upperBound
721: * are considered.
722: * @return The numerical result of parsing this string. This will
723: * be the matching rule's base value, composed appropriately with
724: * the results of matching any of its substitutions. The object
725: * will be an instance of Long if it's an integral value; otherwise,
726: * it will be an instance of Double. This function always returns
727: * a valid object: If nothing matched the input string at all,
728: * this function returns new Long(0), and the parse position is
729: * left unchanged.
730: */
731: public Number parse(String text, ParsePosition parsePosition,
732: double upperBound) {
733: // try matching each rule in the rule set against the text being
734: // parsed. Whichever one matches the most characters is the one
735: // that determines the value we return.
736:
737: ParsePosition highWaterMark = new ParsePosition(0);
738: Number result = new Long(0);
739: Number tempResult = null;
740:
741: // dump out if there's no text to parse
742: if (text.length() == 0) {
743: return result;
744: }
745:
746: // start by trying the nehative number rule (if there is one)
747: if (negativeNumberRule != null) {
748: tempResult = negativeNumberRule.doParse(text,
749: parsePosition, false, upperBound);
750: if (parsePosition.getIndex() > highWaterMark.getIndex()) {
751: result = tempResult;
752: highWaterMark.setIndex(parsePosition.getIndex());
753: }
754: // commented out because the error-index API on ParsePosition isn't there in 1.1.x
755: // if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
756: // highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
757: // }
758: parsePosition.setIndex(0);
759: }
760:
761: // then try each of the fraction rules
762: for (int i = 0; i < 3; i++) {
763: if (fractionRules[i] != null) {
764: tempResult = fractionRules[i].doParse(text,
765: parsePosition, false, upperBound);
766: if (parsePosition.getIndex() > highWaterMark.getIndex()) {
767: result = tempResult;
768: highWaterMark.setIndex(parsePosition.getIndex());
769: }
770: // commented out because the error-index API on ParsePosition isn't there in 1.1.x
771: // if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
772: // highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
773: // }
774: parsePosition.setIndex(0);
775: }
776: }
777:
778: // finally, go through the regular rules one at a time. We start
779: // at the end of the list because we want to try matching the most
780: // sigificant rule first (this helps ensure that we parse
781: // "five thousand three hundred six" as
782: // "(five thousand) (three hundred) (six)" rather than
783: // "((five thousand three) hundred) (six)"). Skip rules whose
784: // base values are higher than the upper bound (again, this helps
785: // limit ambiguity by making sure the rules that match a rule's
786: // are less significant than the rule containing the substitutions)/
787: for (int i = rules.length - 1; i >= 0
788: && highWaterMark.getIndex() < text.length(); i--) {
789: if (!isFractionRuleSet
790: && rules[i].getBaseValue() >= upperBound) {
791: continue;
792: }
793:
794: tempResult = rules[i].doParse(text, parsePosition,
795: isFractionRuleSet, upperBound);
796: if (parsePosition.getIndex() > highWaterMark.getIndex()) {
797: result = tempResult;
798: highWaterMark.setIndex(parsePosition.getIndex());
799: }
800: // commented out because the error-index API on ParsePosition isn't there in 1.1.x
801: // if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
802: // highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
803: // }
804: parsePosition.setIndex(0);
805: }
806:
807: // finally, update the parse postion we were passed to point to the
808: // first character we didn't use, and return the result that
809: // cporresponds to that string of characters
810: parsePosition.setIndex(highWaterMark.getIndex());
811: // commented out because the error-index API on ParsePosition isn't there in 1.1.x
812: // if (parsePosition.getIndex() == 0) {
813: // parsePosition.setErrorIndex(highWaterMark.getErrorIndex());
814: // }
815:
816: return result;
817: }
818: }
|