/* 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 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 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); } } } }