0001: /*
0002: *******************************************************************************
0003: * Copyright (C) 1996-2004, International Business Machines Corporation and *
0004: * others. All Rights Reserved. *
0005: *******************************************************************************
0006: */
0007: package com.ibm.icu.text;
0008:
0009: import com.ibm.icu.impl.UCharacterProperty;
0010:
0011: import java.text.*;
0012:
0013: /**
0014: * A class represnting a single rule in a RuleBasedNumberFormat. A rule
0015: * inserts its text into the result string and then passes control to its
0016: * substitutions, which do the same thing.
0017: */
0018: final class NFRule {
0019: //-----------------------------------------------------------------------
0020: // constants
0021: //-----------------------------------------------------------------------
0022:
0023: /**
0024: * Puts a copyright in the .class file
0025: */
0026: private static final String copyrightNotice = "Copyright \u00a91997-1998 IBM Corp. All rights reserved.";
0027:
0028: /**
0029: * Special base value used to identify a negative-number rule
0030: */
0031: public static final int NEGATIVE_NUMBER_RULE = -1;
0032:
0033: /**
0034: * Special base value used to identify an improper fraction (x.x) rule
0035: */
0036: public static final int IMPROPER_FRACTION_RULE = -2;
0037:
0038: /**
0039: * Special base value used to identify a proper fraction (0.x) rule
0040: */
0041: public static final int PROPER_FRACTION_RULE = -3;
0042:
0043: /**
0044: * Special base value used to identify a master rule
0045: */
0046: public static final int MASTER_RULE = -4;
0047:
0048: //-----------------------------------------------------------------------
0049: // data members
0050: //-----------------------------------------------------------------------
0051:
0052: /**
0053: * The rule's base value
0054: */
0055: private long baseValue;
0056:
0057: /**
0058: * The rule's radix (the radix to the power of the exponent equals
0059: * the rule's divisor)
0060: */
0061: private int radix = 10;
0062:
0063: /**
0064: * The rule's exponent (the radx rased to the power of the exponsnt
0065: * equals the rule's divisor)
0066: */
0067: private short exponent = 0;
0068:
0069: /**
0070: * The rule's rule text. When formatting a number, the rule's text
0071: * is inserted into the result string, and then the text from any
0072: * substitutions is inserted into the result string
0073: */
0074: private String ruleText = null;
0075:
0076: /**
0077: * The rule's first substitution (the one with the lower offset
0078: * into the rule text)
0079: */
0080: private NFSubstitution sub1 = null;
0081:
0082: /**
0083: * The rule's second substitution (the one with the higher offset
0084: * into the rule text)
0085: */
0086: private NFSubstitution sub2 = null;
0087:
0088: /**
0089: * The RuleBasedNumberFormat that owns this rule
0090: */
0091: private RuleBasedNumberFormat formatter = null;
0092:
0093: //-----------------------------------------------------------------------
0094: // construction
0095: //-----------------------------------------------------------------------
0096:
0097: /**
0098: * Creates one or more rules based on the description passed in.
0099: * @param description The description of the rule(s).
0100: * @param owner The rule set containing the new rule(s).
0101: * @param predecessor The rule that precedes the new one(s) in "owner"'s
0102: * rule list
0103: * @param ownersOwner The RuleBasedNumberFormat that owns the
0104: * rule set that owns the new rule(s)
0105: * @return An instance of NFRule, or an array of NFRules
0106: */
0107: public static Object makeRules(String description, NFRuleSet owner,
0108: NFRule predecessor, RuleBasedNumberFormat ownersOwner) {
0109: // we know we're making at least one rule, so go ahead and
0110: // new it up and initialize its basevalue and divisor
0111: // (this also strips the rule descriptor, if any, off the
0112: // descripton string)
0113: NFRule rule1 = new NFRule(ownersOwner);
0114: description = rule1.parseRuleDescriptor(description);
0115:
0116: // check the description to see whether there's text enclosed
0117: // in brackets
0118: int brack1 = description.indexOf("[");
0119: int brack2 = description.indexOf("]");
0120:
0121: // if the description doesn't contain a matched pair of brackets,
0122: // or if it's of a type that doesn't recognize bracketed text,
0123: // then leave the description alone, initialize the rule's
0124: // rule text and substitutions, and return that rule
0125: if (brack1 == -1 || brack2 == -1 || brack1 > brack2
0126: || rule1.getBaseValue() == PROPER_FRACTION_RULE
0127: || rule1.getBaseValue() == NEGATIVE_NUMBER_RULE) {
0128: rule1.ruleText = description;
0129: rule1.extractSubstitutions(owner, predecessor, ownersOwner);
0130: return rule1;
0131: } else {
0132: // if the description does contain a matched pair of brackets,
0133: // then it's really shorthand for two rules (with one exception)
0134: NFRule rule2 = null;
0135: StringBuffer sbuf = new StringBuffer();
0136:
0137: // we'll actually only split the rule into two rules if its
0138: // base value is an even multiple of its divisor (or it's one
0139: // of the special rules)
0140: if ((rule1.baseValue > 0 && rule1.baseValue
0141: % (Math.pow(rule1.radix, rule1.exponent)) == 0)
0142: || rule1.baseValue == IMPROPER_FRACTION_RULE
0143: || rule1.baseValue == MASTER_RULE) {
0144:
0145: // if it passes that test, new up the second rule. If the
0146: // rule set both rules will belong to is a fraction rule
0147: // set, they both have the same base value; otherwise,
0148: // increment the original rule's base value ("rule1" actually
0149: // goes SECOND in the rule set's rule list)
0150: rule2 = new NFRule(ownersOwner);
0151: if (rule1.baseValue >= 0) {
0152: rule2.baseValue = rule1.baseValue;
0153: if (!owner.isFractionSet()) {
0154: ++rule1.baseValue;
0155: }
0156: }
0157:
0158: // if the description began with "x.x" and contains bracketed
0159: // text, it describes both the improper fraction rule and
0160: // the proper fraction rule
0161: else if (rule1.baseValue == IMPROPER_FRACTION_RULE) {
0162: rule2.baseValue = PROPER_FRACTION_RULE;
0163: }
0164:
0165: // if the description began with "x.0" and contains bracketed
0166: // text, it describes both the master rule and the
0167: // improper fraction rule
0168: else if (rule1.baseValue == MASTER_RULE) {
0169: rule2.baseValue = rule1.baseValue;
0170: rule1.baseValue = IMPROPER_FRACTION_RULE;
0171: }
0172:
0173: // both rules have the same radix and exponent (i.e., the
0174: // same divisor)
0175: rule2.radix = rule1.radix;
0176: rule2.exponent = rule1.exponent;
0177:
0178: // rule2's rule text omits the stuff in brackets: initalize
0179: // its rule text and substitutions accordingly
0180: sbuf.append(description.substring(0, brack1));
0181: if (brack2 + 1 < description.length()) {
0182: sbuf.append(description.substring(brack2 + 1));
0183: }
0184: rule2.ruleText = sbuf.toString();
0185: rule2.extractSubstitutions(owner, predecessor,
0186: ownersOwner);
0187: }
0188:
0189: // rule1's text includes the text in the brackets but omits
0190: // the brackets themselves: initialize _its_ rule text and
0191: // substitutions accordingly
0192: sbuf.setLength(0);
0193: sbuf.append(description.substring(0, brack1));
0194: sbuf.append(description.substring(brack1 + 1, brack2));
0195: if (brack2 + 1 < description.length()) {
0196: sbuf.append(description.substring(brack2 + 1));
0197: }
0198: rule1.ruleText = sbuf.toString();
0199: rule1.extractSubstitutions(owner, predecessor, ownersOwner);
0200:
0201: // if we only have one rule, return it; if we have two, return
0202: // a two-element array containing them (notice that rule2 goes
0203: // BEFORE rule1 in the list: in all cases, rule2 OMITS the
0204: // material in the brackets and rule1 INCLUDES the material
0205: // in the brackets)
0206: if (rule2 == null) {
0207: return rule1;
0208: } else {
0209: return new NFRule[] { rule2, rule1 };
0210: }
0211: }
0212: }
0213:
0214: /**
0215: * Nominal constructor for NFRule. Most of the work of constructing
0216: * an NFRule is actually performed by makeRules().
0217: */
0218: public NFRule(RuleBasedNumberFormat formatter) {
0219: this .formatter = formatter;
0220: }
0221:
0222: /**
0223: * This function parses the rule's rule descriptor (i.e., the base
0224: * value and/or other tokens that precede the rule's rule text
0225: * in the description) and sets the rule's base value, radix, and
0226: * exponent according to the descriptor. (If the description doesn't
0227: * include a rule descriptor, then this function sets everything to
0228: * default values and the rule set sets the rule's real base value).
0229: * @param description The rule's description
0230: * @return If "description" included a rule descriptor, this is
0231: * "description" with the descriptor and any trailing whitespace
0232: * stripped off. Otherwise; it's "descriptor" unchangd.
0233: */
0234: private String parseRuleDescriptor(String description) {
0235: String descriptor;
0236:
0237: // the description consists of a rule descriptor and a rule body,
0238: // separated by a colon. The rule descriptor is optional. If
0239: // it's omitted, just set the base value to 0.
0240: int p = description.indexOf(":");
0241: if (p == -1) {
0242: setBaseValue(0);
0243: } else {
0244: // copy the descriptor out into its own string and strip it,
0245: // along with any trailing whitespace, out of the original
0246: // description
0247: descriptor = description.substring(0, p);
0248: ++p;
0249: while (p < description.length()
0250: && UCharacterProperty.isRuleWhiteSpace(description
0251: .charAt(p)))
0252: ++p;
0253: description = description.substring(p);
0254:
0255: // check first to see if the rule descriptor matches the token
0256: // for one of the special rules. If it does, set the base
0257: // value to the correct identfier value
0258: if (descriptor.equals("-x")) {
0259: setBaseValue(NEGATIVE_NUMBER_RULE);
0260: } else if (descriptor.equals("x.x")) {
0261: setBaseValue(IMPROPER_FRACTION_RULE);
0262: } else if (descriptor.equals("0.x")) {
0263: setBaseValue(PROPER_FRACTION_RULE);
0264: } else if (descriptor.equals("x.0")) {
0265: setBaseValue(MASTER_RULE);
0266: }
0267:
0268: // if the rule descriptor begins with a digit, it's a descriptor
0269: // for a normal rule
0270: else if (descriptor.charAt(0) >= '0'
0271: && descriptor.charAt(0) <= '9') {
0272: StringBuffer tempValue = new StringBuffer();
0273: p = 0;
0274: char c = ' ';
0275:
0276: // begin parsing the descriptor: copy digits
0277: // into "tempValue", skip periods, commas, and spaces,
0278: // stop on a slash or > sign (or at the end of the string),
0279: // and throw an exception on any other character
0280: while (p < descriptor.length()) {
0281: c = descriptor.charAt(p);
0282: if (c >= '0' && c <= '9') {
0283: tempValue.append(c);
0284: } else if (c == '/' || c == '>') {
0285: break;
0286: } else if (UCharacterProperty.isRuleWhiteSpace(c)
0287: || c == ',' || c == '.') {
0288: } else {
0289: throw new IllegalArgumentException(
0290: "Illegal character in rule descriptor");
0291: }
0292: ++p;
0293: }
0294:
0295: // tempValue now contains a string representation of the
0296: // rule's base value with the punctuation stripped out.
0297: // Set the rule's base value accordingly
0298: setBaseValue(Long.parseLong(tempValue.toString()));
0299:
0300: // if we stopped the previous loop on a slash, we're
0301: // now parsing the rule's radix. Again, accumulate digits
0302: // in tempValue, skip punctuation, stop on a > mark, and
0303: // throw an exception on anything else
0304: if (c == '/') {
0305: tempValue.setLength(0);
0306: ++p;
0307: while (p < descriptor.length()) {
0308: c = descriptor.charAt(p);
0309: if (c >= '0' && c <= '9') {
0310: tempValue.append(c);
0311: } else if (c == '>') {
0312: break;
0313: } else if (UCharacterProperty
0314: .isRuleWhiteSpace(c)
0315: || c == ',' || c == '.') {
0316: } else {
0317: throw new IllegalArgumentException(
0318: "Illegal character is rule descriptor");
0319: }
0320: ++p;
0321: }
0322:
0323: // tempValue now contain's the rule's radix. Set it
0324: // accordingly, and recalculate the rule's exponent
0325: radix = Integer.parseInt(tempValue.toString());
0326: if (radix == 0) {
0327: throw new IllegalArgumentException(
0328: "Rule can't have radix of 0");
0329: }
0330: exponent = expectedExponent();
0331: }
0332:
0333: // if we stopped the previous loop on a > sign, then continue
0334: // for as long as we still see > signs. For each one,
0335: // decrement the exponent (unless the exponent is already 0).
0336: // If we see another character before reaching the end of
0337: // the descriptor, that's also a syntax error.
0338: if (c == '>') {
0339: while (p < descriptor.length()) {
0340: c = descriptor.charAt(p);
0341: if (c == '>' && exponent > 0) {
0342: --exponent;
0343: } else {
0344: throw new IllegalArgumentException(
0345: "Illegal character in rule descriptor");
0346: }
0347: ++p;
0348: }
0349: }
0350: }
0351: }
0352:
0353: // finally, if the rule body begins with an apostrophe, strip it off
0354: // (this is generally used to put whitespace at the beginning of
0355: // a rule's rule text)
0356: if (description.length() > 0 && description.charAt(0) == '\'') {
0357: description = description.substring(1);
0358: }
0359:
0360: // return the description with all the stuff we've just waded through
0361: // stripped off the front. It now contains just the rule body.
0362: return description;
0363: }
0364:
0365: /**
0366: * Searches the rule's rule text for the substitution tokens,
0367: * creates the substitutions, and removes the substitution tokens
0368: * from the rule's rule text.
0369: * @param owner The rule set containing this rule
0370: * @param predecessor The rule preseding this one in "owners" rule list
0371: * @param ownersOwner The RuleBasedFormat that owns this rule
0372: */
0373: private void extractSubstitutions(NFRuleSet owner,
0374: NFRule predecessor, RuleBasedNumberFormat ownersOwner) {
0375: sub1 = extractSubstitution(owner, predecessor, ownersOwner);
0376: sub2 = extractSubstitution(owner, predecessor, ownersOwner);
0377: }
0378:
0379: /**
0380: * Searches the rule's rule text for the first substitution token,
0381: * creates a substitution based on it, and removes the token from
0382: * the rule's rule text.
0383: * @param owner The rule set containing this rule
0384: * @param predecessor The rule preceding this one in the rule set's
0385: * rule list
0386: * @param ownersOwner The RuleBasedNumberFormat that owns this rule
0387: * @return The newly-created substitution. This is never null; if
0388: * the rule text doesn't contain any substitution tokens, this will
0389: * be a NullSubstitution.
0390: */
0391: private NFSubstitution extractSubstitution(NFRuleSet owner,
0392: NFRule predecessor, RuleBasedNumberFormat ownersOwner) {
0393: NFSubstitution result = null;
0394: int subStart;
0395: int subEnd;
0396:
0397: // search the rule's rule text for the first two characters of
0398: // a substitution token
0399: subStart = indexOfAny(new String[] { "<<", "<%", "<#", "<0",
0400: ">>", ">%", ">#", ">0", "=%", "=#", "=0" });
0401:
0402: // if we didn't find one, create a null substitution positioned
0403: // at the end of the rule text
0404: if (subStart == -1) {
0405: return NFSubstitution.makeSubstitution(ruleText.length(),
0406: this , predecessor, owner, ownersOwner, "");
0407: }
0408:
0409: // special-case the ">>>" token, since searching for the > at the
0410: // end will actually find the > in the middle
0411: if (ruleText.substring(subStart).startsWith(">>>")) {
0412: subEnd = subStart + 2;
0413:
0414: // otherwise the substitution token ends with the same character
0415: // it began with
0416: } else {
0417: char c = ruleText.charAt(subStart);
0418: subEnd = ruleText.indexOf(c, subStart + 1);
0419: // special case for '<%foo<<'
0420: if (c == '<' && subEnd != -1
0421: && subEnd < ruleText.length() - 1
0422: && ruleText.charAt(subEnd + 1) == c) {
0423: // ordinals use "=#,##0==%abbrev=" as their rule. Notice that the '==' in the middle
0424: // occurs because of the juxtaposition of two different rules. The check for '<' is a hack
0425: // to get around this. Having the duplicate at the front would cause problems with
0426: // rules like "<<%" to format, say, percents...
0427: ++subEnd;
0428: }
0429: }
0430:
0431: // if we don't find the end of the token (i.e., if we're on a single,
0432: // unmatched token character), create a null substitution positioned
0433: // at the end of the rule
0434: if (subEnd == -1) {
0435: return NFSubstitution.makeSubstitution(ruleText.length(),
0436: this , predecessor, owner, ownersOwner, "");
0437: }
0438:
0439: // if we get here, we have a real substitution token (or at least
0440: // some text bounded by substitution token characters). Use
0441: // makeSubstitution() to create the right kind of substitution
0442: result = NFSubstitution.makeSubstitution(subStart, this ,
0443: predecessor, owner, ownersOwner, ruleText.substring(
0444: subStart, subEnd + 1));
0445:
0446: // remove the substitution from the rule text
0447: ruleText = ruleText.substring(0, subStart)
0448: + ruleText.substring(subEnd + 1);
0449: return result;
0450: }
0451:
0452: /**
0453: * Sets the rule's base value, and causes the radix and exponent
0454: * to be recalculated. This is used during construction when we
0455: * don't know the rule's base value until after it's been
0456: * constructed. It should not be used at any other time.
0457: * @param The new base value for the rule.
0458: */
0459: public final void setBaseValue(long newBaseValue) {
0460: // set the base value
0461: baseValue = newBaseValue;
0462:
0463: // if this isn't a special rule, recalculate the radix and exponent
0464: // (the radix always defaults to 10; if it's supposed to be something
0465: // else, it's cleaned up by the caller and the exponent is
0466: // recalculated again-- the only function that does this is
0467: // NFRule.parseRuleDescriptor() )
0468: if (baseValue >= 1) {
0469: radix = 10;
0470: exponent = expectedExponent();
0471:
0472: // this function gets called on a fully-constructed rule whose
0473: // description didn't specify a base value. This means it
0474: // has substitutions, and some substitutions hold on to copies
0475: // of the rule's divisor. Fix their copies of the divisor.
0476: if (sub1 != null) {
0477: sub1.setDivisor(radix, exponent);
0478: }
0479: if (sub2 != null) {
0480: sub2.setDivisor(radix, exponent);
0481: }
0482:
0483: // if this is a special rule, its radix and exponent are basically
0484: // ignored. Set them to "safe" default values
0485: } else {
0486: radix = 10;
0487: exponent = 0;
0488: }
0489: }
0490:
0491: /**
0492: * This calculates the rule's exponent based on its radix and base
0493: * value. This will be the highest power the radix can be raised to
0494: * and still produce a result less than or equal to the base value.
0495: */
0496: private short expectedExponent() {
0497: // since the log of 0, or the log base 0 of something, causes an
0498: // error, declare the exponent in these cases to be 0 (we also
0499: // deal with the special-rule identifiers here)
0500: if (radix == 0 || baseValue < 1) {
0501: return 0;
0502: }
0503:
0504: // we get rounding error in some cases-- for example, log 1000 / log 10
0505: // gives us 1.9999999996 instead of 2. The extra logic here is to take
0506: // that into account
0507: short tempResult = (short) (Math.log(baseValue) / Math
0508: .log(radix));
0509: if (Math.pow(radix, tempResult + 1) <= baseValue) {
0510: return (short) (tempResult + 1);
0511: } else {
0512: return tempResult;
0513: }
0514: }
0515:
0516: /**
0517: * Searches the rule's rule text for any of the specified strings.
0518: * @param strings An array of strings to search the rule's rule
0519: * text for
0520: * @return The index of the first match in the rule's rule text
0521: * (i.e., the first substring in the rule's rule text that matches
0522: * _any_ of the strings in "strings"). If none of the strings in
0523: * "strings" is found in the rule's rule text, returns -1.
0524: */
0525: private int indexOfAny(String[] strings) {
0526: int pos;
0527: int result = -1;
0528: for (int i = 0; i < strings.length; i++) {
0529: pos = ruleText.indexOf(strings[i]);
0530: if (pos != -1 && (result == -1 || pos < result)) {
0531: result = pos;
0532: }
0533: }
0534: return result;
0535: }
0536:
0537: //-----------------------------------------------------------------------
0538: // boilerplate
0539: //-----------------------------------------------------------------------
0540:
0541: /**
0542: * Tests two rules for equality.
0543: * @param that The rule to compare this one against
0544: * @return True if the two rules are functionally equivalent
0545: */
0546: public boolean equals(Object that) {
0547: if (that instanceof NFRule) {
0548: NFRule that2 = (NFRule) that;
0549:
0550: return baseValue == that2.baseValue && radix == that2.radix
0551: && exponent == that2.exponent
0552: && ruleText.equals(that2.ruleText)
0553: && sub1.equals(that2.sub1)
0554: && sub2.equals(that2.sub2);
0555: }
0556: return false;
0557: }
0558:
0559: /**
0560: * Returns a textual representation of the rule. This won't
0561: * necessarily be the same as the description that this rule
0562: * was created with, but it will produce the same result.
0563: * @return A textual description of the rule
0564: */
0565: public String toString() {
0566: StringBuffer result = new StringBuffer();
0567:
0568: // start with the rule descriptor. Special-case the special rules
0569: if (baseValue == NEGATIVE_NUMBER_RULE) {
0570: result.append("-x: ");
0571: } else if (baseValue == IMPROPER_FRACTION_RULE) {
0572: result.append("x.x: ");
0573: } else if (baseValue == PROPER_FRACTION_RULE) {
0574: result.append("0.x: ");
0575: } else if (baseValue == MASTER_RULE) {
0576: result.append("x.0: ");
0577: }
0578:
0579: // for a normal rule, write out its base value, and if the radix is
0580: // something other than 10, write out the radix (with the preceding
0581: // slash, of course). Then calculate the expected exponent and if
0582: // if isn't the same as the actual exponent, write an appropriate
0583: // number of > signs. Finally, terminate the whole thing with
0584: // a colon.
0585: else {
0586: result.append(String.valueOf(baseValue));
0587: if (radix != 10) {
0588: result.append('/');
0589: result.append(String.valueOf(radix));
0590: }
0591: int numCarets = expectedExponent() - exponent;
0592: for (int i = 0; i < numCarets; i++)
0593: result.append('>');
0594: result.append(": ");
0595: }
0596:
0597: // if the rule text begins with a space, write an apostrophe
0598: // (whitespace after the rule descriptor is ignored; the
0599: // apostrophe is used to make the whitespace significant)
0600: if (ruleText.startsWith(" ")
0601: && (sub1 == null || sub1.getPos() != 0)) {
0602: result.append("\'");
0603: }
0604:
0605: // now, write the rule's rule text, inserting appropriate
0606: // substitution tokens in the appropriate places
0607: StringBuffer ruleTextCopy = new StringBuffer(ruleText);
0608: ruleTextCopy.insert(sub2.getPos(), sub2.toString());
0609: ruleTextCopy.insert(sub1.getPos(), sub1.toString());
0610: result.append(ruleTextCopy.toString());
0611:
0612: // and finally, top the whole thing off with a semicolon and
0613: // return the result
0614: result.append(';');
0615: return result.toString();
0616: }
0617:
0618: //-----------------------------------------------------------------------
0619: // simple accessors
0620: //-----------------------------------------------------------------------
0621:
0622: /**
0623: * Returns the rule's base value
0624: * @return The rule's base value
0625: */
0626: public final long getBaseValue() {
0627: return baseValue;
0628: }
0629:
0630: /**
0631: * Returns the rule's divisor (the value that cotrols the behavior
0632: * of its substitutions)
0633: * @return The rule's divisor
0634: */
0635: public double getDivisor() {
0636: return Math.pow(radix, exponent);
0637: }
0638:
0639: //-----------------------------------------------------------------------
0640: // formatting
0641: //-----------------------------------------------------------------------
0642:
0643: /**
0644: * Formats the number, and inserts the resulting text into
0645: * toInsertInto.
0646: * @param number The number being formatted
0647: * @param toInsertInto The string where the resultant text should
0648: * be inserted
0649: * @param pos The position in toInsertInto where the resultant text
0650: * should be inserted
0651: */
0652: public void doFormat(long number, StringBuffer toInsertInto, int pos) {
0653: // first, insert the rule's rule text into toInsertInto at the
0654: // specified position, then insert the results of the substitutions
0655: // into the right places in toInsertInto (notice we do the
0656: // substitutions in reverse order so that the offsets don't get
0657: // messed up)
0658: toInsertInto.insert(pos, ruleText);
0659: sub2.doSubstitution(number, toInsertInto, pos);
0660: sub1.doSubstitution(number, toInsertInto, pos);
0661: }
0662:
0663: /**
0664: * Formats the number, and inserts the resulting text into
0665: * toInsertInto.
0666: * @param number The number being formatted
0667: * @param toInsertInto The string where the resultant text should
0668: * be inserted
0669: * @param pos The position in toInsertInto where the resultant text
0670: * should be inserted
0671: */
0672: public void doFormat(double number, StringBuffer toInsertInto,
0673: int pos) {
0674: // first, insert the rule's rule text into toInsertInto at the
0675: // specified position, then insert the results of the substitutions
0676: // into the right places in toInsertInto
0677: // [again, we have two copies of this routine that do the same thing
0678: // so that we don't sacrifice precision in a long by casting it
0679: // to a double]
0680: toInsertInto.insert(pos, ruleText);
0681: sub2.doSubstitution(number, toInsertInto, pos);
0682: sub1.doSubstitution(number, toInsertInto, pos);
0683: }
0684:
0685: /**
0686: * Used by the owning rule set to determine whether to invoke the
0687: * rollback rule (i.e., whether this rule or the one that precedes
0688: * it in the rule set's list should be used to format the number)
0689: * @param The number being formatted
0690: * @return True if the rule set should use the rule that precedes
0691: * this one in its list; false if it should use this rule
0692: */
0693: public boolean shouldRollBack(double number) {
0694: // we roll back if the rule contains a modulus substitution,
0695: // the number being formatted is an even multiple of the rule's
0696: // divisor, and the rule's base value is NOT an even multiple
0697: // of its divisor
0698: // In other words, if the original description had
0699: // 100: << hundred[ >>];
0700: // that expands into
0701: // 100: << hundred;
0702: // 101: << hundred >>;
0703: // internally. But when we're formatting 200, if we use the rule
0704: // at 101, which would normally apply, we get "two hundred zero".
0705: // To prevent this, we roll back and use the rule at 100 instead.
0706: // This is the logic that makes this happen: the rule at 101 has
0707: // a modulus substitution, its base value isn't an even multiple
0708: // of 100, and the value we're trying to format _is_ an even
0709: // multiple of 100. This is called the "rollback rule."
0710: if ((sub1.isModulusSubstitution())
0711: || (sub2.isModulusSubstitution())) {
0712: return (number % Math.pow(radix, exponent)) == 0
0713: && (baseValue % Math.pow(radix, exponent)) != 0;
0714: }
0715: return false;
0716: }
0717:
0718: //-----------------------------------------------------------------------
0719: // parsing
0720: //-----------------------------------------------------------------------
0721:
0722: /**
0723: * Attempts to parse the string with this rule.
0724: * @param text The string being parsed
0725: * @param parsePosition On entry, the value is ignored and assumed to
0726: * be 0. On exit, this has been updated with the position of the first
0727: * character not consumed by matching the text against this rule
0728: * (if this rule doesn't match the text at all, the parse position
0729: * if left unchanged (presumably at 0) and the function returns
0730: * new Long(0)).
0731: * @param isFractionRule True if this rule is contained within a
0732: * fraction rule set. This is only used if the rule has no
0733: * substitutions.
0734: * @return If this rule matched the text, this is the rule's base value
0735: * combined appropriately with the results of parsing the substitutions.
0736: * If nothing matched, this is new Long(0) and the parse position is
0737: * left unchanged. The result will be an instance of Long if the
0738: * result is an integer and Double otherwise. The result is never null.
0739: */
0740: public Number doParse(String text, ParsePosition parsePosition,
0741: boolean isFractionRule, double upperBound) {
0742:
0743: // internally we operate on a copy of the string being parsed
0744: // (because we're going to change it) and use our own ParsePosition
0745: ParsePosition pp = new ParsePosition(0);
0746: String workText = new String(text);
0747:
0748: // check to see whether the text before the first substitution
0749: // matches the text at the beginning of the string being
0750: // parsed. If it does, strip that off the front of workText;
0751: // otherwise, dump out with a mismatch
0752: workText = stripPrefix(workText, ruleText.substring(0, sub1
0753: .getPos()), pp);
0754: int prefixLength = text.length() - workText.length();
0755:
0756: if (pp.getIndex() == 0 && sub1.getPos() != 0) {
0757: // commented out because ParsePosition doesn't have error index in 1.1.x
0758: // parsePosition.setErrorIndex(pp.getErrorIndex());
0759: return new Long(0);
0760: }
0761:
0762: // this is the fun part. The basic guts of the rule-matching
0763: // logic is matchToDelimiter(), which is called twice. The first
0764: // time it searches the input string for the rule text BETWEEN
0765: // the substitutions and tries to match the intervening text
0766: // in the input string with the first substitution. If that
0767: // succeeds, it then calls it again, this time to look for the
0768: // rule text after the second substitution and to match the
0769: // intervening input text against the second substitution.
0770: //
0771: // For example, say we have a rule that looks like this:
0772: // first << middle >> last;
0773: // and input text that looks like this:
0774: // first one middle two last
0775: // First we use stripPrefix() to match "first " in both places and
0776: // strip it off the front, leaving
0777: // one middle two last
0778: // Then we use matchToDelimiter() to match " middle " and try to
0779: // match "one" against a substitution. If it's successful, we now
0780: // have
0781: // two last
0782: // We use matchToDelimiter() a second time to match " last" and
0783: // try to match "two" against a substitution. If "two" matches
0784: // the substitution, we have a successful parse.
0785: //
0786: // Since it's possible in many cases to find multiple instances
0787: // of each of these pieces of rule text in the input string,
0788: // we need to try all the possible combinations of these
0789: // locations. This prevents us from prematurely declaring a mismatch,
0790: // and makes sure we match as much input text as we can.
0791: int highWaterMark = 0;
0792: double result = 0;
0793: int start = 0;
0794: double tempBaseValue = Math.max(0, baseValue);
0795:
0796: do {
0797: // our partial parse result starts out as this rule's base
0798: // value. If it finds a successful match, matchToDelimiter()
0799: // will compose this in some way with what it gets back from
0800: // the substitution, giving us a new partial parse result
0801: pp.setIndex(0);
0802: double partialResult = matchToDelimiter(workText, start,
0803: tempBaseValue,
0804: ruleText.substring(sub1.getPos(), sub2.getPos()),
0805: pp, sub1, upperBound).doubleValue();
0806:
0807: // if we got a successful match (or were trying to match a
0808: // null substitution), pp is now pointing at the first unmatched
0809: // character. Take note of that, and try matchToDelimiter()
0810: // on the input text again
0811: if (pp.getIndex() != 0 || sub1.isNullSubstitution()) {
0812: start = pp.getIndex();
0813:
0814: String workText2 = workText.substring(pp.getIndex());
0815: ParsePosition pp2 = new ParsePosition(0);
0816:
0817: // the second matchToDelimiter() will compose our previous
0818: // partial result with whatever it gets back from its
0819: // substitution if there's a successful match, giving us
0820: // a real result
0821: partialResult = matchToDelimiter(workText2, 0,
0822: partialResult,
0823: ruleText.substring(sub2.getPos()), pp2, sub2,
0824: upperBound).doubleValue();
0825:
0826: // if we got a successful match on this second
0827: // matchToDelimiter() call, update the high-water mark
0828: // and result (if necessary)
0829: if (pp2.getIndex() != 0 || sub2.isNullSubstitution()) {
0830: if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) {
0831: highWaterMark = prefixLength + pp.getIndex()
0832: + pp2.getIndex();
0833: result = partialResult;
0834: }
0835: }
0836: // commented out because ParsePosition doesn't have error index in 1.1.x
0837: // else {
0838: // int temp = pp2.getErrorIndex() + sub1.getPos() + pp.getIndex();
0839: // if (temp> parsePosition.getErrorIndex()) {
0840: // parsePosition.setErrorIndex(temp);
0841: // }
0842: // }
0843: }
0844: // commented out because ParsePosition doesn't have error index in 1.1.x
0845: // else {
0846: // int temp = sub1.getPos() + pp.getErrorIndex();
0847: // if (temp > parsePosition.getErrorIndex()) {
0848: // parsePosition.setErrorIndex(temp);
0849: // }
0850: // }
0851: // keep trying to match things until the outer matchToDelimiter()
0852: // call fails to make a match (each time, it picks up where it
0853: // left off the previous time)
0854: } while (sub1.getPos() != sub2.getPos() && pp.getIndex() > 0
0855: && pp.getIndex() < workText.length()
0856: && pp.getIndex() != start);
0857:
0858: // update the caller's ParsePosition with our high-water mark
0859: // (i.e., it now points at the first character this function
0860: // didn't match-- the ParsePosition is therefore unchanged if
0861: // we didn't match anything)
0862: parsePosition.setIndex(highWaterMark);
0863: // commented out because ParsePosition doesn't have error index in 1.1.x
0864: // if (highWaterMark > 0) {
0865: // parsePosition.setErrorIndex(0);
0866: // }
0867:
0868: // this is a hack for one unusual condition: Normally, whether this
0869: // rule belong to a fraction rule set or not is handled by its
0870: // substitutions. But if that rule HAS NO substitutions, then
0871: // we have to account for it here. By definition, if the matching
0872: // rule in a fraction rule set has no substitutions, its numerator
0873: // is 1, and so the result is the reciprocal of its base value.
0874: if (isFractionRule && highWaterMark > 0
0875: && sub1.isNullSubstitution()) {
0876: result = 1 / result;
0877: }
0878:
0879: // return the result as a Long if possible, or as a Double
0880: if (result == (long) result) {
0881: return new Long((long) result);
0882: } else {
0883: return new Double(result);
0884: }
0885: }
0886:
0887: /**
0888: * This function is used by parse() to match the text being parsed
0889: * against a possible prefix string. This function
0890: * matches characters from the beginning of the string being parsed
0891: * to characters from the prospective prefix. If they match, pp is
0892: * updated to the first character not matched, and the result is
0893: * the unparsed part of the string. If they don't match, the whole
0894: * string is returned, and pp is left unchanged.
0895: * @param text The string being parsed
0896: * @param prefix The text to match against
0897: * @param pp On entry, ignored and assumed to be 0. On exit, points
0898: * to the first unmatched character (assuming the whole prefix matched),
0899: * or is unchanged (if the whole prefix didn't match).
0900: * @return If things match, this is the unparsed part of "text";
0901: * if they didn't match, this is "text".
0902: */
0903: private String stripPrefix(String text, String prefix,
0904: ParsePosition pp) {
0905: // if the prefix text is empty, dump out without doing anything
0906: if (prefix.length() == 0) {
0907: return text;
0908: } else {
0909: // otherwise, use prefixLength() to match the beginning of
0910: // "text" against "prefix". This function returns the
0911: // number of characters from "text" that matched (or 0 if
0912: // we didn't match the whole prefix)
0913: int pfl = prefixLength(text, prefix);
0914: if (pfl != 0) {
0915: // if we got a successful match, update the parse position
0916: // and strip the prefix off of "text"
0917: pp.setIndex(pp.getIndex() + pfl);
0918: return text.substring(pfl);
0919:
0920: // if we didn't get a successful match, leave everything alone
0921: } else {
0922: return text;
0923: }
0924: }
0925: }
0926:
0927: /**
0928: * Used by parse() to match a substitution and any following text.
0929: * "text" is searched for instances of "delimiter". For each instance
0930: * of delimiter, the intervening text is tested to see whether it
0931: * matches the substitution. The longest match wins.
0932: * @param text The string being parsed
0933: * @param startPos The position in "text" where we should start looking
0934: * for "delimiter".
0935: * @param baseValue A partial parse result (often the rule's base value),
0936: * which is combined with the result from matching the substitution
0937: * @param delimiter The string to search "text" for.
0938: * @param pp Ignored and presumed to be 0 on entry. If there's a match,
0939: * on exit this will point to the first unmatched character.
0940: * @param sub If we find "delimiter" in "text", this substitution is used
0941: * to match the text between the beginning of the string and the
0942: * position of "delimiter." (If "delimiter" is the empty string, then
0943: * this function just matches against this substitution and updates
0944: * everything accordingly.)
0945: * @param upperBound When matching the substitution, it will only
0946: * consider rules with base values lower than this value.
0947: * @return If there's a match, this is the result of composing
0948: * baseValue with the result of matching the substitution. Otherwise,
0949: * this is new Long(0). It's never null. If the result is an integer,
0950: * this will be an instance of Long; otherwise, it's an instance of
0951: * Double.
0952: */
0953: private Number matchToDelimiter(String text, int startPos,
0954: double baseValue, String delimiter, ParsePosition pp,
0955: NFSubstitution sub, double upperBound) {
0956: // if "delimiter" contains real (i.e., non-ignorable) text, search
0957: // it for "delimiter" beginning at "start". If that succeeds, then
0958: // use "sub"'s doParse() method to match the text before the
0959: // instance of "delimiter" we just found.
0960: if (!allIgnorable(delimiter)) {
0961: ParsePosition tempPP = new ParsePosition(0);
0962: Number tempResult;
0963:
0964: // use findText() to search for "delimiter". It returns a two-
0965: // element array: element 0 is the position of the match, and
0966: // element 1 is the number of characters that matched
0967: // "delimiter".
0968: int[] temp = findText(text, delimiter, startPos);
0969: int dPos = temp[0];
0970: int dLen = temp[1];
0971:
0972: // if findText() succeeded, isolate the text preceding the
0973: // match, and use "sub" to match that text
0974: while (dPos >= 0) {
0975: String subText = text.substring(0, dPos);
0976: if (subText.length() > 0) {
0977: tempResult = sub.doParse(subText, tempPP,
0978: baseValue, upperBound, formatter
0979: .lenientParseEnabled());
0980:
0981: // if the substitution could match all the text up to
0982: // where we found "delimiter", then this function has
0983: // a successful match. Bump the caller's parse position
0984: // to point to the first character after the text
0985: // that matches "delimiter", and return the result
0986: // we got from parsing the substitution.
0987: if (tempPP.getIndex() == dPos) {
0988: pp.setIndex(dPos + dLen);
0989: return tempResult;
0990: }
0991: // commented out because ParsePosition doesn't have error index in 1.1.x
0992: // else {
0993: // if (tempPP.getErrorIndex() > 0) {
0994: // pp.setErrorIndex(tempPP.getErrorIndex());
0995: // } else {
0996: // pp.setErrorIndex(tempPP.getIndex());
0997: // }
0998: // }
0999: }
1000:
1001: // if we didn't match the substitution, search for another
1002: // copy of "delimiter" in "text" and repeat the loop if
1003: // we find it
1004: tempPP.setIndex(0);
1005: temp = findText(text, delimiter, dPos + dLen);
1006: dPos = temp[0];
1007: dLen = temp[1];
1008: }
1009: // if we make it here, this was an unsuccessful match, and we
1010: // leave pp unchanged and return 0
1011: pp.setIndex(0);
1012: return new Long(0);
1013:
1014: // if "delimiter" is empty, or consists only of ignorable characters
1015: // (i.e., is semantically empty), thwe we obviously can't search
1016: // for "delimiter". Instead, just use "sub" to parse as much of
1017: // "text" as possible.
1018: } else {
1019: ParsePosition tempPP = new ParsePosition(0);
1020: Number result = new Long(0);
1021: Number tempResult;
1022:
1023: // try to match the whole string against the substitution
1024: tempResult = sub.doParse(text, tempPP, baseValue,
1025: upperBound, formatter.lenientParseEnabled());
1026: if (tempPP.getIndex() != 0 || sub.isNullSubstitution()) {
1027: // if there's a successful match (or it's a null
1028: // substitution), update pp to point to the first
1029: // character we didn't match, and pass the result from
1030: // sub.doParse() on through to the caller
1031: pp.setIndex(tempPP.getIndex());
1032: if (tempResult != null) {
1033: result = tempResult;
1034: }
1035: }
1036: // commented out because ParsePosition doesn't have error index in 1.1.x
1037: // else {
1038: // pp.setErrorIndex(tempPP.getErrorIndex());
1039: // }
1040:
1041: // and if we get to here, then nothing matched, so we return
1042: // 0 and leave pp alone
1043: return result;
1044: }
1045: }
1046:
1047: /**
1048: * Used by stripPrefix() to match characters. If lenient parse mode
1049: * is off, this just calls startsWith(). If lenient parse mode is on,
1050: * this function uses CollationElementIterators to match characters in
1051: * the strings (only primary-order differences are significant in
1052: * determining whether there's a match).
1053: * @param str The string being tested
1054: * @param prefix The text we're hoping to see at the beginning
1055: * of "str"
1056: * @return If "prefix" is found at the beginning of "str", this
1057: * is the number of characters in "str" that were matched (this
1058: * isn't necessarily the same as the length of "prefix" when matching
1059: * text with a collator). If there's no match, this is 0.
1060: */
1061: private int prefixLength(String str, String prefix) {
1062: // if we're looking for an empty prefix, it obviously matches
1063: // zero characters. Just go ahead and return 0.
1064: if (prefix.length() == 0) {
1065: return 0;
1066: }
1067:
1068: // go through all this grief if we're in lenient-parse mode
1069: if (formatter.lenientParseEnabled()) {
1070: // get the formatter's collator and use it to create two
1071: // collation element iterators, one over the target string
1072: // and another over the prefix (right now, we'll throw an
1073: // exception if the collator we get back from the formatter
1074: // isn't a RuleBasedCollator, because RuleBasedCollator defines
1075: // the CollationElementIteratoer protocol. Hopefully, this
1076: // will change someday.)
1077: //
1078: // Previous code was matching "fifty-" against " fifty" and leaving
1079: // the number " fifty-7" to parse as 43 (50 - 7).
1080: // Also it seems that if we consume the entire prefix, that's ok even
1081: // if we've consumed the entire string, so I switched the logic to
1082: // reflect this.
1083: RuleBasedCollator collator = (RuleBasedCollator) formatter
1084: .getCollator();
1085: CollationElementIterator strIter = collator
1086: .getCollationElementIterator(str);
1087: CollationElementIterator prefixIter = collator
1088: .getCollationElementIterator(prefix);
1089:
1090: // match collation elements between the strings
1091: int oStr = strIter.next();
1092: int oPrefix = prefixIter.next();
1093:
1094: while (oPrefix != CollationElementIterator.NULLORDER) {
1095: // skip over ignorable characters in the target string
1096: while (CollationElementIterator.primaryOrder(oStr) == 0
1097: && oStr != CollationElementIterator.NULLORDER) {
1098: oStr = strIter.next();
1099: }
1100:
1101: // skip over ignorable characters in the prefix
1102: while (CollationElementIterator.primaryOrder(oPrefix) == 0
1103: && oPrefix != CollationElementIterator.NULLORDER) {
1104: oPrefix = prefixIter.next();
1105: }
1106:
1107: // if skipping over ignorables brought to the end of
1108: // the prefix, we DID match: drop out of the loop
1109: if (oPrefix == CollationElementIterator.NULLORDER) {
1110: break;
1111: }
1112:
1113: // if skipping over ignorables brought us to the end
1114: // of the target string, we didn't match and return 0
1115: if (oStr == CollationElementIterator.NULLORDER) {
1116: return 0;
1117: }
1118:
1119: // match collation elements from the two strings
1120: // (considering only primary differences). If we
1121: // get a mismatch, dump out and return 0
1122: if (CollationElementIterator.primaryOrder(oStr) != CollationElementIterator
1123: .primaryOrder(oPrefix)) {
1124: return 0;
1125: }
1126: // otherwise, advance to the next character in each string
1127: // and loop (we drop out of the loop when we exhaust
1128: // collation elements in the prefix)
1129:
1130: oStr = strIter.next();
1131: oPrefix = prefixIter.next();
1132: }
1133:
1134: // we are not compatible with jdk 1.1 any longer
1135: int result = strIter.getOffset();
1136: if (oStr != CollationElementIterator.NULLORDER) {
1137: --result;
1138: }
1139: return result;
1140:
1141: /*
1142: //----------------------------------------------------------------
1143: // JDK 1.2-specific API call
1144: // return strIter.getOffset();
1145: //----------------------------------------------------------------
1146: // JDK 1.1 HACK (take out for 1.2-specific code)
1147:
1148: // if we make it to here, we have a successful match. Now we
1149: // have to find out HOW MANY characters from the target string
1150: // matched the prefix (there isn't necessarily a one-to-one
1151: // mapping between collation elements and characters).
1152: // In JDK 1.2, there's a simple getOffset() call we can use.
1153: // In JDK 1.1, on the other hand, we have to go through some
1154: // ugly contortions. First, use the collator to compare the
1155: // same number of characters from the prefix and target string.
1156: // If they're equal, we're done.
1157: collator.setStrength(Collator.PRIMARY);
1158: if (str.length() >= prefix.length()
1159: && collator.equals(str.substring(0, prefix.length()), prefix)) {
1160: return prefix.length();
1161: }
1162:
1163: // if they're not equal, then we have to compare successively
1164: // larger and larger substrings of the target string until we
1165: // get to one that matches the prefix. At that point, we know
1166: // how many characters matched the prefix, and we can return.
1167: int p = 1;
1168: while (p <= str.length()) {
1169: if (collator.equals(str.substring(0, p), prefix)) {
1170: return p;
1171: } else {
1172: ++p;
1173: }
1174: }
1175:
1176: // SHOULKD NEVER GET HERE!!!
1177: return 0;
1178: //----------------------------------------------------------------
1179: */
1180:
1181: // If lenient parsing is turned off, forget all that crap above.
1182: // Just use String.startsWith() and be done with it.
1183: } else {
1184: if (str.startsWith(prefix)) {
1185: return prefix.length();
1186: } else {
1187: return 0;
1188: }
1189: }
1190: }
1191:
1192: /**
1193: * Searches a string for another string. If lenient parsing is off,
1194: * this just calls indexOf(). If lenient parsing is on, this function
1195: * uses CollationElementIterator to match characters, and only
1196: * primary-order differences are significant in determining whether
1197: * there's a match.
1198: * @param str The string to search
1199: * @param key The string to search "str" for
1200: * @return A two-element array of ints. Element 0 is the position
1201: * of the match, or -1 if there was no match. Element 1 is the
1202: * number of characters in "str" that matched (which isn't necessarily
1203: * the same as the length of "key")
1204: */
1205: private int[] findText(String str, String key) {
1206: return findText(str, key, 0);
1207: }
1208:
1209: /**
1210: * Searches a string for another string. If lenient parsing is off,
1211: * this just calls indexOf(). If lenient parsing is on, this function
1212: * uses CollationElementIterator to match characters, and only
1213: * primary-order differences are significant in determining whether
1214: * there's a match.
1215: * @param str The string to search
1216: * @param key The string to search "str" for
1217: * @param startingAt The index into "str" where the search is to
1218: * begin
1219: * @return A two-element array of ints. Element 0 is the position
1220: * of the match, or -1 if there was no match. Element 1 is the
1221: * number of characters in "str" that matched (which isn't necessarily
1222: * the same as the length of "key")
1223: */
1224: private int[] findText(String str, String key, int startingAt) {
1225: // if lenient parsing is turned off, this is easy: just call
1226: // String.indexOf() and we're done
1227: if (!formatter.lenientParseEnabled()) {
1228: return new int[] { str.indexOf(key, startingAt),
1229: key.length() };
1230:
1231: // but if lenient parsing is turned ON, we've got some work
1232: // ahead of us
1233: } else {
1234: //----------------------------------------------------------------
1235: // JDK 1.1 HACK (take out of 1.2-specific code)
1236:
1237: // in JDK 1.2, CollationElementIterator provides us with an
1238: // API to map between character offsets and collation elements
1239: // and we can do this by marching through the string comparing
1240: // collation elements. We can't do that in JDK 1.1. Insted,
1241: // we have to go through this horrible slow mess:
1242: int p = startingAt;
1243: int keyLen = 0;
1244:
1245: // basically just isolate smaller and smaller substrings of
1246: // the target string (each running to the end of the string,
1247: // and with the first one running from startingAt to the end)
1248: // and then use prefixLength() to see if the search key is at
1249: // the beginning of each substring. This is excruciatingly
1250: // slow, but it will locate the key and tell use how long the
1251: // matching text was.
1252: while (p < str.length() && keyLen == 0) {
1253: keyLen = prefixLength(str.substring(p), key);
1254: if (keyLen != 0) {
1255: return new int[] { p, keyLen };
1256: }
1257: ++p;
1258: }
1259: // if we make it to here, we didn't find it. Return -1 for the
1260: // location. The length should be ignored, but set it to 0,
1261: // which should be "safe"
1262: return new int[] { -1, 0 };
1263:
1264: //----------------------------------------------------------------
1265: // JDK 1.2 version of this routine
1266: //RuleBasedCollator collator = (RuleBasedCollator)formatter.getCollator();
1267: //
1268: //CollationElementIterator strIter = collator.getCollationElementIterator(str);
1269: //CollationElementIterator keyIter = collator.getCollationElementIterator(key);
1270: //
1271: //int keyStart = -1;
1272: //
1273: //str.setOffset(startingAt);
1274: //
1275: //int oStr = strIter.next();
1276: //int oKey = keyIter.next();
1277: //while (oKey != CollationElementIterator.NULLORDER) {
1278: // while (oStr != CollationElementIterator.NULLORDER &&
1279: // CollationElementIterator.primaryOrder(oStr) == 0)
1280: // oStr = strIter.next();
1281: //
1282: // while (oKey != CollationElementIterator.NULLORDER &&
1283: // CollationElementIterator.primaryOrder(oKey) == 0)
1284: // oKey = keyIter.next();
1285: //
1286: // if (oStr == CollationElementIterator.NULLORDER) {
1287: // return new int[] { -1, 0 };
1288: // }
1289: //
1290: // if (oKey == CollationElementIterator.NULLORDER) {
1291: // break;
1292: // }
1293: //
1294: // if (CollationElementIterator.primaryOrder(oStr) ==
1295: // CollationElementIterator.primaryOrder(oKey)) {
1296: // keyStart = strIter.getOffset();
1297: // oStr = strIter.next();
1298: // oKey = keyIter.next();
1299: // } else {
1300: // if (keyStart != -1) {
1301: // keyStart = -1;
1302: // keyIter.reset();
1303: // } else {
1304: // oStr = strIter.next();
1305: // }
1306: // }
1307: //}
1308: //
1309: //if (oKey == CollationElementIterator.NULLORDER) {
1310: // return new int[] { keyStart, strIter.getOffset() - keyStart };
1311: //} else {
1312: // return new int[] { -1, 0 };
1313: //}
1314: }
1315: }
1316:
1317: /**
1318: * Checks to see whether a string consists entirely of ignorable
1319: * characters.
1320: * @param str The string to test.
1321: * @return true if the string is empty of consists entirely of
1322: * characters that the number formatter's collator says are
1323: * ignorable at the primary-order level. false otherwise.
1324: */
1325: private boolean allIgnorable(String str) {
1326: // if the string is empty, we can just return true
1327: if (str.length() == 0) {
1328: return true;
1329: }
1330:
1331: // if lenient parsing is turned on, walk through the string with
1332: // a collation element iterator and make sure each collation
1333: // element is 0 (ignorable) at the primary level
1334: if (formatter.lenientParseEnabled()) {
1335: RuleBasedCollator collator = (RuleBasedCollator) (formatter
1336: .getCollator());
1337: CollationElementIterator iter = collator
1338: .getCollationElementIterator(str);
1339:
1340: int o = iter.next();
1341: while (o != CollationElementIterator.NULLORDER
1342: && CollationElementIterator.primaryOrder(o) == 0) {
1343: o = iter.next();
1344: }
1345: return o == CollationElementIterator.NULLORDER;
1346: // if lenient parsing is turned off, there is no such thing as
1347: // an ignorable character: return true only if the string is empty
1348: } else {
1349: return false;
1350: }
1351: }
1352: }
|