Source Code Cross Referenced for NFRule.java in  » Internationalization-Localization » icu4j » com » ibm » icu » text » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Internationalization Localization » icu4j » com.ibm.icu.text 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.