| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2015, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| package android.icu.text; |
| |
| import java.text.ParsePosition; |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import android.icu.impl.PatternProps; |
| |
| /** |
| * A collection of rules used by a RuleBasedNumberFormat to format and |
| * parse numbers. It is the responsibility of a RuleSet to select an |
| * appropriate rule for formatting a particular number and dispatch |
| * control to it, and to arbitrate between different rules when parsing |
| * a number. |
| */ |
| |
| final class NFRuleSet { |
| //----------------------------------------------------------------------- |
| // data members |
| //----------------------------------------------------------------------- |
| |
| /** |
| * The rule set's name |
| */ |
| private final String name; |
| |
| /** |
| * The rule set's regular rules |
| */ |
| private NFRule[] rules; |
| |
| /** |
| * The rule set's non-numerical rules like negative, fractions, infinity and NaN |
| */ |
| final NFRule[] nonNumericalRules = new NFRule[6]; |
| |
| /** |
| * These are a pile of fraction rules in declared order. They may have alternate |
| * ways to represent fractions. |
| */ |
| LinkedList<NFRule> fractionRules; |
| |
| /** -x */ |
| static final int NEGATIVE_RULE_INDEX = 0; |
| /** x.x */ |
| static final int IMPROPER_FRACTION_RULE_INDEX = 1; |
| /** 0.x */ |
| static final int PROPER_FRACTION_RULE_INDEX = 2; |
| /** x.0 */ |
| static final int DEFAULT_RULE_INDEX = 3; |
| /** Inf */ |
| static final int INFINITY_RULE_INDEX = 4; |
| /** NaN */ |
| static final int NAN_RULE_INDEX = 5; |
| |
| /** |
| * The RuleBasedNumberFormat that owns this rule |
| */ |
| final RuleBasedNumberFormat owner; |
| |
| /** |
| * True if the rule set is a fraction rule set. A fraction rule set |
| * is a rule set that is used to format the fractional part of a |
| * number. It is called from a >> substitution in another rule set's |
| * fraction rule, and is only called upon to format values between |
| * 0 and 1. A fraction rule set has different rule-selection |
| * behavior than a regular rule set. |
| */ |
| private boolean isFractionRuleSet = false; |
| |
| /** |
| * True if the rule set is parseable. |
| */ |
| private final boolean isParseable; |
| |
| /** |
| * Limit of recursion. It's about a 64 bit number formatted in base 2. |
| */ |
| private static final int RECURSION_LIMIT = 64; |
| |
| //----------------------------------------------------------------------- |
| // construction |
| //----------------------------------------------------------------------- |
| |
| /** |
| * Constructs a rule set. |
| * @param owner The formatter that owns this rule set |
| * @param descriptions An array of Strings representing rule set |
| * descriptions. On exit, this rule set's entry in the array will |
| * have been stripped of its rule set name and any trailing whitespace. |
| * @param index The index into "descriptions" of the description |
| * for the rule to be constructed |
| */ |
| public NFRuleSet(RuleBasedNumberFormat owner, String[] descriptions, int index) throws IllegalArgumentException { |
| this.owner = owner; |
| String description = descriptions[index]; |
| |
| if (description.length() == 0) { |
| throw new IllegalArgumentException("Empty rule set description"); |
| } |
| |
| // if the description begins with a rule set name (the rule set |
| // name can be omitted in formatter descriptions that consist |
| // of only one rule set), copy it out into our "name" member |
| // and delete it from the description |
| if (description.charAt(0) == '%') { |
| int pos = description.indexOf(':'); |
| if (pos == -1) { |
| throw new IllegalArgumentException("Rule set name doesn't end in colon"); |
| } |
| else { |
| String name = description.substring(0, pos); |
| this.isParseable = !name.endsWith("@noparse"); |
| if (!this.isParseable) { |
| name = name.substring(0,name.length()-8); // Remove the @noparse from the name |
| } |
| this.name = name; |
| |
| //noinspection StatementWithEmptyBody |
| while (pos < description.length() && PatternProps.isWhiteSpace(description.charAt(++pos))) { |
| } |
| description = description.substring(pos); |
| descriptions[index] = description; |
| } |
| } |
| else { |
| // if the description doesn't begin with a rule set name, its |
| // name is "%default" |
| name = "%default"; |
| isParseable = true; |
| } |
| |
| if (description.length() == 0) { |
| throw new IllegalArgumentException("Empty rule set description"); |
| } |
| |
| // all of the other members of NFRuleSet are initialized |
| // by parseRules() |
| } |
| |
| /** |
| * Construct the subordinate data structures used by this object. |
| * This function is called by the RuleBasedNumberFormat constructor |
| * after all the rule sets have been created to actually parse |
| * the description and build rules from it. Since any rule set |
| * can refer to any other rule set, we have to have created all of |
| * them before we can create anything else. |
| * @param description The textual description of this rule set |
| */ |
| public void parseRules(String description) { |
| // (the number of elements in the description list isn't necessarily |
| // the number of rules-- some descriptions may expend into two rules) |
| List<NFRule> tempRules = new ArrayList<>(); |
| |
| // we keep track of the rule before the one we're currently working |
| // on solely to support >>> substitutions |
| NFRule predecessor = null; |
| |
| // Iterate through the rules. The rules |
| // are separated by semicolons (there's no escape facility: ALL |
| // semicolons are rule delimiters) |
| int oldP = 0; |
| int descriptionLen = description.length(); |
| int p; |
| do { |
| p = description.indexOf(';', oldP); |
| if (p < 0) { |
| p = descriptionLen; |
| } |
| |
| // makeRules (a factory method on NFRule) will return either |
| // a single rule or an array of rules. Either way, add them |
| // to our rule vector |
| NFRule.makeRules(description.substring(oldP, p), |
| this, predecessor, owner, tempRules); |
| if (!tempRules.isEmpty()) { |
| predecessor = tempRules.get(tempRules.size() - 1); |
| } |
| |
| oldP = p + 1; |
| } |
| while (oldP < descriptionLen); |
| |
| // for rules that didn't specify a base value, their base values |
| // were initialized to 0. Make another pass through the list and |
| // set all those rules' base values. We also remove any special |
| // rules from the list and put them into their own member variables |
| long defaultBaseValue = 0; |
| |
| for (NFRule rule : tempRules) { |
| long baseValue = rule.getBaseValue(); |
| if (baseValue == 0) { |
| // if the rule's base value is 0, fill in a default |
| // base value (this will be 1 plus the preceding |
| // rule's base value for regular rule sets, and the |
| // same as the preceding rule's base value in fraction |
| // rule sets) |
| rule.setBaseValue(defaultBaseValue); |
| } |
| else { |
| // if it's a regular rule that already knows its base value, |
| // check to make sure the rules are in order, and update |
| // the default base value for the next rule |
| if (baseValue < defaultBaseValue) { |
| throw new IllegalArgumentException("Rules are not in order, base: " + |
| baseValue + " < " + defaultBaseValue); |
| } |
| defaultBaseValue = baseValue; |
| } |
| if (!isFractionRuleSet) { |
| ++defaultBaseValue; |
| } |
| } |
| |
| // finally, we can copy the rules from the vector into a |
| // fixed-length array |
| rules = new NFRule[tempRules.size()]; |
| tempRules.toArray(rules); |
| } |
| |
| /** |
| * Set one of the non-numerical rules. |
| * @param rule The rule to set. |
| */ |
| void setNonNumericalRule(NFRule rule) { |
| long baseValue = rule.getBaseValue(); |
| if (baseValue == NFRule.NEGATIVE_NUMBER_RULE) { |
| nonNumericalRules[NFRuleSet.NEGATIVE_RULE_INDEX] = rule; |
| } |
| else if (baseValue == NFRule.IMPROPER_FRACTION_RULE) { |
| setBestFractionRule(NFRuleSet.IMPROPER_FRACTION_RULE_INDEX, rule, true); |
| } |
| else if (baseValue == NFRule.PROPER_FRACTION_RULE) { |
| setBestFractionRule(NFRuleSet.PROPER_FRACTION_RULE_INDEX, rule, true); |
| } |
| else if (baseValue == NFRule.DEFAULT_RULE) { |
| setBestFractionRule(NFRuleSet.DEFAULT_RULE_INDEX, rule, true); |
| } |
| else if (baseValue == NFRule.INFINITY_RULE) { |
| nonNumericalRules[NFRuleSet.INFINITY_RULE_INDEX] = rule; |
| } |
| else if (baseValue == NFRule.NAN_RULE) { |
| nonNumericalRules[NFRuleSet.NAN_RULE_INDEX] = rule; |
| } |
| } |
| |
| /** |
| * Determine the best fraction rule to use. Rules matching the decimal point from |
| * DecimalFormatSymbols become the main set of rules to use. |
| * @param originalIndex The index into nonNumericalRules |
| * @param newRule The new rule to consider |
| * @param rememberRule Should the new rule be added to fractionRules. |
| */ |
| private void setBestFractionRule(int originalIndex, NFRule newRule, boolean rememberRule) { |
| if (rememberRule) { |
| if (fractionRules == null) { |
| fractionRules = new LinkedList<>(); |
| } |
| fractionRules.add(newRule); |
| } |
| NFRule bestResult = nonNumericalRules[originalIndex]; |
| if (bestResult == null) { |
| nonNumericalRules[originalIndex] = newRule; |
| } |
| else { |
| // We have more than one. Which one is better? |
| DecimalFormatSymbols decimalFormatSymbols = owner.getDecimalFormatSymbols(); |
| if (decimalFormatSymbols.getDecimalSeparator() == newRule.getDecimalPoint()) { |
| nonNumericalRules[originalIndex] = newRule; |
| } |
| // else leave it alone |
| } |
| } |
| |
| /** |
| * Flags this rule set as a fraction rule set. This function is |
| * called during the construction process once we know this rule |
| * set is a fraction rule set. We don't know a rule set is a |
| * fraction rule set until we see it used somewhere. This function |
| * is not ad must not be called at any time other than during |
| * construction of a RuleBasedNumberFormat. |
| */ |
| public void makeIntoFractionRuleSet() { |
| isFractionRuleSet = true; |
| } |
| |
| //----------------------------------------------------------------------- |
| // boilerplate |
| //----------------------------------------------------------------------- |
| |
| /** |
| * Compares two rule sets for equality. |
| * @param that The other rule set |
| * @return true if the two rule sets are functionally equivalent. |
| */ |
| @Override |
| public boolean equals(Object that) { |
| // if different classes, they're not equal |
| if (!(that instanceof NFRuleSet)) { |
| return false; |
| } else { |
| // otherwise, compare the members one by one... |
| NFRuleSet that2 = (NFRuleSet)that; |
| |
| if (!name.equals(that2.name) |
| || rules.length != that2.rules.length |
| || isFractionRuleSet != that2.isFractionRuleSet) |
| { |
| return false; |
| } |
| |
| // ...then compare the non-numerical rule lists... |
| for (int i = 0; i < nonNumericalRules.length; i++) { |
| if (!Objects.equals(nonNumericalRules[i], that2.nonNumericalRules[i])) { |
| return false; |
| } |
| } |
| |
| // ...then compare the rule lists... |
| for (int i = 0; i < rules.length; i++) { |
| if (!rules[i].equals(that2.rules[i])) { |
| return false; |
| } |
| } |
| |
| // ...and if we make it here, they're equal |
| return true; |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| assert false : "hashCode not designed"; |
| return 42; |
| } |
| |
| |
| /** |
| * Builds a textual representation of a rule set. |
| * @return A textual representation of a rule set. This won't |
| * necessarily be the same description that the rule set was |
| * constructed with, but it will produce the same results. |
| */ |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| |
| // the rule set name goes first... |
| result.append(name).append(":\n"); |
| |
| // followed by the regular rules... |
| for (NFRule rule : rules) { |
| result.append(rule.toString()).append("\n"); |
| } |
| |
| // followed by the special rules (if they exist) |
| for (NFRule rule : nonNumericalRules) { |
| if (rule != null) { |
| if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE |
| || rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE |
| || rule.getBaseValue() == NFRule.DEFAULT_RULE) |
| { |
| for (NFRule fractionRule : fractionRules) { |
| if (fractionRule.getBaseValue() == rule.getBaseValue()) { |
| result.append(fractionRule.toString()).append("\n"); |
| } |
| } |
| } |
| else { |
| result.append(rule.toString()).append("\n"); |
| } |
| } |
| } |
| |
| return result.toString(); |
| } |
| |
| //----------------------------------------------------------------------- |
| // simple accessors |
| //----------------------------------------------------------------------- |
| |
| /** |
| * Says whether this rule set is a fraction rule set. |
| * @return true if this rule is a fraction rule set; false if it isn't |
| */ |
| public boolean isFractionSet() { |
| return isFractionRuleSet; |
| } |
| |
| /** |
| * Returns the rule set's name |
| * @return The rule set's name |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Return true if the rule set is public. |
| * @return true if the rule set is public |
| */ |
| public boolean isPublic() { |
| return !name.startsWith("%%"); |
| } |
| |
| /** |
| * Return true if the rule set can be used for parsing. |
| * @return true if the rule set can be used for parsing. |
| */ |
| public boolean isParseable() { |
| return isParseable; |
| } |
| |
| //----------------------------------------------------------------------- |
| // formatting |
| //----------------------------------------------------------------------- |
| |
| /** |
| * Formats a long. Selects an appropriate rule and dispatches |
| * control to it. |
| * @param number The number being formatted |
| * @param toInsertInto The string where the result is to be placed |
| * @param pos The position in toInsertInto where the result of |
| * this operation is to be inserted |
| */ |
| public void format(long number, StringBuilder toInsertInto, int pos, int recursionCount) { |
| if (recursionCount >= RECURSION_LIMIT) { |
| throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name); |
| } |
| NFRule applicableRule = findNormalRule(number); |
| applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount); |
| } |
| |
| /** |
| * Formats a double. Selects an appropriate rule and dispatches |
| * control to it. |
| * @param number The number being formatted |
| * @param toInsertInto The string where the result is to be placed |
| * @param pos The position in toInsertInto where the result of |
| * this operation is to be inserted |
| */ |
| public void format(double number, StringBuilder toInsertInto, int pos, int recursionCount) { |
| if (recursionCount >= RECURSION_LIMIT) { |
| throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name); |
| } |
| NFRule applicableRule = findRule(number); |
| applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount); |
| } |
| |
| /** |
| * Selects an appropriate rule for formatting the number. |
| * @param number The number being formatted. |
| * @return The rule that should be used to format it |
| */ |
| NFRule findRule(double number) { |
| // if this is a fraction rule set, use findFractionRuleSetRule() |
| if (isFractionRuleSet) { |
| return findFractionRuleSetRule(number); |
| } |
| |
| if (Double.isNaN(number)) { |
| NFRule rule = nonNumericalRules[NAN_RULE_INDEX]; |
| if (rule == null) { |
| rule = owner.getDefaultNaNRule(); |
| } |
| return rule; |
| } |
| |
| // if the number is negative, return the negative number rule |
| // (if there isn't a negative-number rule, we pretend it's a |
| // positive number) |
| if (number < 0) { |
| if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) { |
| return nonNumericalRules[NEGATIVE_RULE_INDEX]; |
| } else { |
| number = -number; |
| } |
| } |
| |
| if (Double.isInfinite(number)) { |
| NFRule rule = nonNumericalRules[INFINITY_RULE_INDEX]; |
| if (rule == null) { |
| rule = owner.getDefaultInfinityRule(); |
| } |
| return rule; |
| } |
| |
| // if the number isn't an integer, we use one f the fraction rules... |
| if (number != Math.floor(number)) { |
| if (number < 1 && nonNumericalRules[PROPER_FRACTION_RULE_INDEX] != null) { |
| // if the number is between 0 and 1, return the proper |
| // fraction rule |
| return nonNumericalRules[PROPER_FRACTION_RULE_INDEX]; |
| } |
| else if (nonNumericalRules[IMPROPER_FRACTION_RULE_INDEX] != null) { |
| // otherwise, return the improper fraction rule |
| return nonNumericalRules[IMPROPER_FRACTION_RULE_INDEX]; |
| } |
| } |
| |
| // if there's a default rule, use it to format the number |
| if (nonNumericalRules[DEFAULT_RULE_INDEX] != null) { |
| return nonNumericalRules[DEFAULT_RULE_INDEX]; |
| } |
| else { |
| // and if we haven't yet returned a rule, use findNormalRule() |
| // to find the applicable rule |
| return findNormalRule(Math.round(number)); |
| } |
| } |
| |
| /** |
| * If the value passed to findRule() is a positive integer, findRule() |
| * uses this function to select the appropriate rule. The result will |
| * generally be the rule with the highest base value less than or equal |
| * to the number. There is one exception to this: If that rule has |
| * two substitutions and a base value that is not an even multiple of |
| * its divisor, and the number itself IS an even multiple of the rule's |
| * divisor, then the result will be the rule that preceded the original |
| * result in the rule list. (This behavior is known as the "rollback |
| * rule", and is used to handle optional text: a rule with optional |
| * text is represented internally as two rules, and the rollback rule |
| * selects appropriate between them. This avoids things like "two |
| * hundred zero".) |
| * @param number The number being formatted |
| * @return The rule to use to format this number |
| */ |
| private NFRule findNormalRule(long number) { |
| // if this is a fraction rule set, use findFractionRuleSetRule() |
| // to find the rule (we should only go into this clause if the |
| // value is 0) |
| if (isFractionRuleSet) { |
| return findFractionRuleSetRule(number); |
| } |
| |
| // if the number is negative, return the negative-number rule |
| // (if there isn't one, pretend the number is positive) |
| if (number < 0) { |
| if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) { |
| return nonNumericalRules[NEGATIVE_RULE_INDEX]; |
| } else { |
| number = -number; |
| } |
| } |
| |
| // we have to repeat the preceding two checks, even though we |
| // do them in findRule(), because the version of format() that |
| // takes a long bypasses findRule() and goes straight to this |
| // function. This function does skip the fraction rules since |
| // we know the value is an integer (it also skips the default |
| // rule, since it's considered a fraction rule. Skipping the |
| // default rule in this function is also how we avoid infinite |
| // recursion) |
| |
| // binary-search the rule list for the applicable rule |
| // (a rule is used for all values from its base value to |
| // the next rule's base value) |
| int lo = 0; |
| int hi = rules.length; |
| if (hi > 0) { |
| while (lo < hi) { |
| int mid = (lo + hi) >>> 1; |
| long ruleBaseValue = rules[mid].getBaseValue(); |
| if (ruleBaseValue == number) { |
| return rules[mid]; |
| } |
| else if (ruleBaseValue > number) { |
| hi = mid; |
| } |
| else { |
| lo = mid + 1; |
| } |
| } |
| if (hi == 0) { // bad rule set |
| throw new IllegalStateException("The rule set " + name + " cannot format the value " + number); |
| } |
| NFRule result = rules[hi - 1]; |
| |
| // use shouldRollBack() to see whether we need to invoke the |
| // rollback rule (see shouldRollBack()'s documentation for |
| // an explanation of the rollback rule). If we do, roll back |
| // one rule and return that one instead of the one we'd normally |
| // return |
| if (result.shouldRollBack(number)) { |
| if (hi == 1) { // bad rule set |
| throw new IllegalStateException("The rule set " + name + " cannot roll back from the rule '" + |
| result + "'"); |
| } |
| result = rules[hi - 2]; |
| } |
| return result; |
| } |
| // else use the default rule |
| return nonNumericalRules[DEFAULT_RULE_INDEX]; |
| } |
| |
| /** |
| * If this rule is a fraction rule set, this function is used by |
| * findRule() to select the most appropriate rule for formatting |
| * the number. Basically, the base value of each rule in the rule |
| * set is treated as the denominator of a fraction. Whichever |
| * denominator can produce the fraction closest in value to the |
| * number passed in is the result. If there's a tie, the earlier |
| * one in the list wins. (If there are two rules in a row with the |
| * same base value, the first one is used when the numerator of the |
| * fraction would be 1, and the second rule is used the rest of the |
| * time. |
| * @param number The number being formatted (which will always be |
| * a number between 0 and 1) |
| * @return The rule to use to format this number |
| */ |
| private NFRule findFractionRuleSetRule(double number) { |
| // the obvious way to do this (multiply the value being formatted |
| // by each rule's base value until you get an integral result) |
| // doesn't work because of rounding error. This method is more |
| // accurate |
| |
| // find the least common multiple of the rules' base values |
| // and multiply this by the number being formatted. This is |
| // all the precision we need, and we can do all of the rest |
| // of the math using integer arithmetic |
| long leastCommonMultiple = rules[0].getBaseValue(); |
| for (int i = 1; i < rules.length; i++) { |
| leastCommonMultiple = lcm(leastCommonMultiple, rules[i].getBaseValue()); |
| } |
| long numerator = Math.round(number * leastCommonMultiple); |
| |
| // for each rule, do the following... |
| long tempDifference; |
| long difference = Long.MAX_VALUE; |
| int winner = 0; |
| for (int i = 0; i < rules.length; i++) { |
| // "numerator" is the numerator of the fraction is the |
| // denominator is the LCD. The numerator if the the rule's |
| // base value is the denominator is "numerator" times the |
| // base value divided by the LCD. Here we check to see if |
| // that's an integer, and if not, how close it is to being |
| // an integer. |
| tempDifference = numerator * rules[i].getBaseValue() % leastCommonMultiple; |
| |
| // normalize the result of the above calculation: we want |
| // the numerator's distance from the CLOSEST multiple |
| // of the LCD |
| if (leastCommonMultiple - tempDifference < tempDifference) { |
| tempDifference = leastCommonMultiple - tempDifference; |
| } |
| |
| // if this is as close as we've come, keep track of how close |
| // that is, and the line number of the rule that did it. If |
| // we've scored a direct hit, we don't have to look at any more |
| // rules |
| if (tempDifference < difference) { |
| difference = tempDifference; |
| winner = i; |
| if (difference == 0) { |
| break; |
| } |
| } |
| } |
| |
| // if we have two successive rules that both have the winning base |
| // value, then the first one (the one we found above) is used if |
| // the numerator of the fraction is 1 and the second one is used if |
| // the numerator of the fraction is anything else (this lets us |
| // do things like "one third"/"two thirds" without having to define |
| // a whole bunch of extra rule sets) |
| if (winner + 1 < rules.length |
| && rules[winner + 1].getBaseValue() == rules[winner].getBaseValue()) { |
| if (Math.round(number * rules[winner].getBaseValue()) < 1 |
| || Math.round(number * rules[winner].getBaseValue()) >= 2) { |
| ++winner; |
| } |
| } |
| |
| // finally, return the winning rule |
| return rules[winner]; |
| } |
| |
| /** |
| * Calculates the least common multiple of x and y. |
| */ |
| private static long lcm(long x, long y) { |
| // binary gcd algorithm from Knuth, "The Art of Computer Programming," |
| // vol. 2, 1st ed., pp. 298-299 |
| long x1 = x; |
| long y1 = y; |
| |
| int p2 = 0; |
| while ((x1 & 1) == 0 && (y1 & 1) == 0) { |
| ++p2; |
| x1 >>= 1; |
| y1 >>= 1; |
| } |
| |
| long t; |
| if ((x1 & 1) == 1) { |
| t = -y1; |
| } else { |
| t = x1; |
| } |
| |
| while (t != 0) { |
| while ((t & 1) == 0) { |
| t >>= 1; |
| } |
| if (t > 0) { |
| x1 = t; |
| } else { |
| y1 = -t; |
| } |
| t = x1 - y1; |
| } |
| long gcd = x1 << p2; |
| |
| // x * y == gcd(x, y) * lcm(x, y) |
| return x / gcd * y; |
| } |
| |
| //----------------------------------------------------------------------- |
| // parsing |
| //----------------------------------------------------------------------- |
| |
| /** |
| * Parses a string. Matches the string to be parsed against each |
| * of its rules (with a base value less than upperBound) and returns |
| * the value produced by the rule that matched the most characters |
| * in the source string. |
| * @param text The string to parse |
| * @param parsePosition The initial position is ignored and assumed |
| * to be 0. On exit, this object has been updated to point to the |
| * first character position this rule set didn't consume. |
| * @param upperBound Limits the rules that can be allowed to match. |
| * Only rules whose base values are strictly less than upperBound |
| * are considered. |
| * @return The numerical result of parsing this string. This will |
| * be the matching rule's base value, composed appropriately with |
| * the results of matching any of its substitutions. The object |
| * will be an instance of Long if it's an integral value; otherwise, |
| * it will be an instance of Double. This function always returns |
| * a valid object: If nothing matched the input string at all, |
| * this function returns Long.valueOf(0), and the parse position is |
| * left unchanged. |
| */ |
| public Number parse(String text, ParsePosition parsePosition, double upperBound, int nonNumericalExecutedRuleMask) { |
| // try matching each rule in the rule set against the text being |
| // parsed. Whichever one matches the most characters is the one |
| // that determines the value we return. |
| |
| ParsePosition highWaterMark = new ParsePosition(0); |
| Number result = NFRule.ZERO; |
| Number tempResult; |
| |
| // dump out if there's no text to parse |
| if (text.length() == 0) { |
| return result; |
| } |
| |
| // Try each of the negative rules, fraction rules, infinity rules and NaN rules |
| for (int nonNumericalRuleIdx = 0; nonNumericalRuleIdx < nonNumericalRules.length; nonNumericalRuleIdx++) { |
| NFRule nonNumericalRule = nonNumericalRules[nonNumericalRuleIdx]; |
| if (nonNumericalRule != null && ((nonNumericalExecutedRuleMask >> nonNumericalRuleIdx) & 1) == 0) { |
| // Mark this rule as being executed so that we don't try to execute it again. |
| nonNumericalExecutedRuleMask |= 1 << nonNumericalRuleIdx; |
| |
| tempResult = nonNumericalRule.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask); |
| if (parsePosition.getIndex() > highWaterMark.getIndex()) { |
| result = tempResult; |
| highWaterMark.setIndex(parsePosition.getIndex()); |
| } |
| // commented out because the error-index API on ParsePosition isn't there in 1.1.x |
| // if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) { |
| // highWaterMark.setErrorIndex(parsePosition.getErrorIndex()); |
| // } |
| parsePosition.setIndex(0); |
| } |
| } |
| |
| // finally, go through the regular rules one at a time. We start |
| // at the end of the list because we want to try matching the most |
| // significant rule first (this helps ensure that we parse |
| // "five thousand three hundred six" as |
| // "(five thousand) (three hundred) (six)" rather than |
| // "((five thousand three) hundred) (six)"). Skip rules whose |
| // base values are higher than the upper bound (again, this helps |
| // limit ambiguity by making sure the rules that match a rule's |
| // are less significant than the rule containing the substitutions)/ |
| for (int i = rules.length - 1; i >= 0 && highWaterMark.getIndex() < text.length(); i--) { |
| if (!isFractionRuleSet && rules[i].getBaseValue() >= upperBound) { |
| continue; |
| } |
| |
| tempResult = rules[i].doParse(text, parsePosition, isFractionRuleSet, upperBound, nonNumericalExecutedRuleMask); |
| if (parsePosition.getIndex() > highWaterMark.getIndex()) { |
| result = tempResult; |
| highWaterMark.setIndex(parsePosition.getIndex()); |
| } |
| // commented out because the error-index API on ParsePosition isn't there in 1.1.x |
| // if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) { |
| // highWaterMark.setErrorIndex(parsePosition.getErrorIndex()); |
| // } |
| parsePosition.setIndex(0); |
| } |
| |
| // finally, update the parse position we were passed to point to the |
| // first character we didn't use, and return the result that |
| // corresponds to that string of characters |
| parsePosition.setIndex(highWaterMark.getIndex()); |
| // commented out because the error-index API on ParsePosition isn't there in 1.1.x |
| // if (parsePosition.getIndex() == 0) { |
| // parsePosition.setErrorIndex(highWaterMark.getErrorIndex()); |
| // } |
| |
| return result; |
| } |
| |
| public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { |
| for (NFRule rule : rules) { |
| rule.setDecimalFormatSymbols(newSymbols); |
| } |
| // Switch the fraction rules to mirror the DecimalFormatSymbols. |
| if (fractionRules != null) { |
| for (int nonNumericalIdx = IMPROPER_FRACTION_RULE_INDEX; nonNumericalIdx <= DEFAULT_RULE_INDEX; nonNumericalIdx++) { |
| if (nonNumericalRules[nonNumericalIdx] != null) { |
| for (NFRule rule : fractionRules) { |
| if (nonNumericalRules[nonNumericalIdx].getBaseValue() == rule.getBaseValue()) { |
| setBestFractionRule(nonNumericalIdx, rule, false); |
| } |
| } |
| } |
| } |
| } |
| |
| for (NFRule rule : nonNumericalRules) { |
| if (rule != null) { |
| rule.setDecimalFormatSymbols(newSymbols); |
| } |
| } |
| } |
| } |