/* 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 android.icu.impl.number.DecimalQuantity_DualStorageBCD; import android.icu.math.BigDecimal; //=================================================================== // NFSubstitution (abstract base class) //=================================================================== /** * An abstract class defining protocol for substitutions. A substitution * is a section of a rule that inserts text into the rule's rule text * based on some part of the number being formatted. * @author Richard Gillam */ abstract class NFSubstitution { //----------------------------------------------------------------------- // data members //----------------------------------------------------------------------- /** * The substitution's position in the rule text of the rule that owns it */ final int pos; /** * The rule set this substitution uses to format its result, or null. * (Either this or numberFormat has to be non-null.) */ final NFRuleSet ruleSet; /** * The DecimalFormat this substitution uses to format its result, * or null. (Either this or ruleSet has to be non-null.) */ final DecimalFormat numberFormat; //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Parses the description, creates the right kind of substitution, * and initializes it based on the description. * @param pos The substitution's position in the rule text of the * rule that owns it. * @param rule The rule containing this substitution * @param rulePredecessor The rule preceding the one that contains * this substitution in the rule set's rule list (this is used * only for >>> substitutions). * @param ruleSet The rule set containing the rule containing this * substitution * @param formatter The RuleBasedNumberFormat that ultimately owns * this substitution * @param description The description to parse to build the substitution * (this is just the substring of the rule's description containing * the substitution token itself) * @return A new substitution constructed according to the description */ public static NFSubstitution makeSubstitution(int pos, NFRule rule, NFRule rulePredecessor, NFRuleSet ruleSet, RuleBasedNumberFormat formatter, String description) { // if the description is empty, return a NullSubstitution if (description.length() == 0) { return null; } switch (description.charAt(0)) { case '<': if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) { // throw an exception if the rule is a negative number rule ///CLOVER:OFF // If you look at the call hierarchy of this method, the rule would // never be directly modified by the user and therefore makes the // following pointless unless the user changes the ruleset. throw new IllegalArgumentException("<< not allowed in negative-number rule"); ///CLOVER:ON } else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE || rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE || rule.getBaseValue() == NFRule.DEFAULT_RULE) { // if the rule is a fraction rule, return an IntegralPartSubstitution return new IntegralPartSubstitution(pos, ruleSet, description); } else if (ruleSet.isFractionSet()) { // if the rule set containing the rule is a fraction // rule set, return a NumeratorSubstitution return new NumeratorSubstitution(pos, rule.getBaseValue(), formatter.getDefaultRuleSet(), description); } else { // otherwise, return a MultiplierSubstitution return new MultiplierSubstitution(pos, rule, ruleSet, description); } case '>': if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) { // if the rule is a negative-number rule, return // an AbsoluteValueSubstitution return new AbsoluteValueSubstitution(pos, ruleSet, description); } else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE || rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE || rule.getBaseValue() == NFRule.DEFAULT_RULE) { // if the rule is a fraction rule, return a // FractionalPartSubstitution return new FractionalPartSubstitution(pos, ruleSet, description); } else if (ruleSet.isFractionSet()) { // if the rule set owning the rule is a fraction rule set, // throw an exception ///CLOVER:OFF // If you look at the call hierarchy of this method, the rule would // never be directly modified by the user and therefore makes the // following pointless unless the user changes the ruleset. throw new IllegalArgumentException(">> not allowed in fraction rule set"); ///CLOVER:ON } else { // otherwise, return a ModulusSubstitution return new ModulusSubstitution(pos, rule, rulePredecessor, ruleSet, description); } case '=': return new SameValueSubstitution(pos, ruleSet, description); default: // and if it's anything else, throw an exception ///CLOVER:OFF // If you look at the call hierarchy of this method, the rule would // never be directly modified by the user and therefore makes the // following pointless unless the user changes the ruleset. throw new IllegalArgumentException("Illegal substitution character"); ///CLOVER:ON } } /** * Base constructor for substitutions. This constructor sets up the * fields which are common to all substitutions. * @param pos The substitution's position in the owning rule's rule * text * @param ruleSet The rule set that owns this substitution * @param description The substitution descriptor (i.e., the text * inside the token characters) */ NFSubstitution(int pos, NFRuleSet ruleSet, String description) { // initialize the substitution's position in its parent rule this.pos = pos; int descriptionLen = description.length(); // the description should begin and end with the same character. // If it doesn't that's a syntax error. Otherwise, // makeSubstitution() was the only thing that needed to know // about these characters, so strip them off if (descriptionLen >= 2 && description.charAt(0) == description.charAt(descriptionLen - 1)) { description = description.substring(1, descriptionLen - 1); } else if (descriptionLen != 0) { throw new IllegalArgumentException("Illegal substitution syntax"); } // if the description was just two paired token characters // (i.e., "<<" or ">>"), it uses the rule set it belongs to to // format its result if (description.length() == 0) { this.ruleSet = ruleSet; this.numberFormat = null; } else if (description.charAt(0) == '%') { // if the description contains a rule set name, that's the rule // set we use to format the result: get a reference to the // names rule set this.ruleSet = ruleSet.owner.findRuleSet(description); this.numberFormat = null; } else if (description.charAt(0) == '#' || description.charAt(0) == '0') { // if the description begins with 0 or #, treat it as a // DecimalFormat pattern, and initialize a DecimalFormat with // that pattern (then set it to use the DecimalFormatSymbols // belonging to our formatter) this.ruleSet = null; this.numberFormat = (DecimalFormat) ruleSet.owner.getDecimalFormat().clone(); this.numberFormat.applyPattern(description); } else if (description.charAt(0) == '>') { // if the description is ">>>", this substitution bypasses the // usual rule-search process and always uses the rule that precedes // it in its own rule set's rule list (this is used for place-value // notations: formats where you want to see a particular part of // a number even when it's 0) this.ruleSet = ruleSet; // was null, thai rules added to control space this.numberFormat = null; } else { // and of the description is none of these things, it's a syntax error throw new IllegalArgumentException("Illegal substitution syntax"); } } /** * Set's the substitution's divisor. Used by NFRule.setBaseValue(). * A no-op for all substitutions except multiplier and modulus * substitutions. * @param radix The radix of the divisor * @param exponent The exponent of the divisor */ public void setDivisor(int radix, short exponent) { // a no-op for all substitutions except multiplier and modulus substitutions } //----------------------------------------------------------------------- // boilerplate //----------------------------------------------------------------------- /** * Compares two substitutions for equality * @param that The substitution to compare this one to * @return true if the two substitutions are functionally equivalent */ @Override public boolean equals(Object that) { // compare class and all of the fields all substitutions have // in common if (that == null) { return false; } if (this == that) { return true; } if (this.getClass() == that.getClass()) { NFSubstitution that2 = (NFSubstitution)that; return pos == that2.pos && (ruleSet != null || that2.ruleSet == null) // can't compare tree structure, no .equals or recurse && (numberFormat == null ? (that2.numberFormat == null) : numberFormat.equals(that2.numberFormat)); } return false; } @Override public int hashCode() { assert false : "hashCode not designed"; return 42; } /** * Returns a textual description of the substitution * @return A textual description of the substitution. This might * not be identical to the description it was created from, but * it'll produce the same result. */ @Override public String toString() { // use tokenChar() to get the character at the beginning and // end of the substitution token. In between them will go // either the name of the rule set it uses, or the pattern of // the DecimalFormat it uses if (ruleSet != null) { return tokenChar() + ruleSet.getName() + tokenChar(); } else { return tokenChar() + numberFormat.toPattern() + tokenChar(); } } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- private static final long MAX_INT64_IN_DOUBLE = 0x1FFFFFFFFFFFFFL; /** * Performs a mathematical operation on the number, formats it using * either ruleSet or decimalFormat, and inserts the result into * toInsertInto. * @param number The number being formatted. * @param toInsertInto The string we insert the result into * @param position The position in toInsertInto where the owning rule's * rule text begins (this value is added to this substitution's * position to determine exactly where to insert the new text) */ public void doSubstitution(long number, StringBuilder toInsertInto, int position, int recursionCount) { if (ruleSet != null) { // Perform a transformation on the number that is dependent // on the type of substitution this is, then just call its // rule set's format() method to format the result long numberToFormat = transformNumber(number); ruleSet.format(numberToFormat, toInsertInto, position + pos, recursionCount); } else { if (number <= MAX_INT64_IN_DOUBLE) { // or perform the transformation on the number, // then use that formatter's format() method // to format the result toInsertInto.insert(position + pos, numberFormat.format(transformNumber((double) number))); } else { // We have gone beyond double precision. Something has to give. // We're favoring accuracy of the large number over potential rules // that round like a CompactDecimalFormat, which is not a common use case. // // Perform a transformation on the number that is dependent // on the type of substitution this is, then just call its // rule set's format() method to format the result long numberToFormat = transformNumber(number); toInsertInto.insert(position + pos, numberFormat.format(numberToFormat)); } } } /** * Performs a mathematical operation on the number, formats it using * either ruleSet or decimalFormat, and inserts the result into * toInsertInto. * @param number The number being formatted. * @param toInsertInto The string we insert the result into * @param position The position in toInsertInto where the owning rule's * rule text begins (this value is added to this substitution's * position to determine exactly where to insert the new text) */ public void doSubstitution(double number, StringBuilder toInsertInto, int position, int recursionCount) { // perform a transformation on the number being formatted that // is dependent on the type of substitution this is double numberToFormat = transformNumber(number); if (Double.isInfinite(numberToFormat)) { // This is probably a minus rule. Combine it with an infinite rule. NFRule infiniteRule = ruleSet.findRule(Double.POSITIVE_INFINITY); infiniteRule.doFormat(numberToFormat, toInsertInto, position + pos, recursionCount); return; } // if the result is an integer, from here on out we work in integer // space (saving time and memory and preserving accuracy) if (numberToFormat == Math.floor(numberToFormat) && ruleSet != null) { ruleSet.format((long)numberToFormat, toInsertInto, position + pos, recursionCount); // if the result isn't an integer, then call either our rule set's // format() method or our DecimalFormat's format() method to // format the result } else { if (ruleSet != null) { ruleSet.format(numberToFormat, toInsertInto, position + pos, recursionCount); } else { toInsertInto.insert(position + this.pos, numberFormat.format(numberToFormat)); } } } /** * Subclasses override this function to perform some kind of * mathematical operation on the number. The result of this operation * is formatted using the rule set or DecimalFormat that this * substitution refers to, and the result is inserted into the result * string. * @param number The number being formatted * @return The result of performing the opreration on the number */ public abstract long transformNumber(long number); /** * Subclasses override this function to perform some kind of * mathematical operation on the number. The result of this operation * is formatted using the rule set or DecimalFormat that this * substitution refers to, and the result is inserted into the result * string. * @param number The number being formatted * @return The result of performing the opreration on the number */ public abstract double transformNumber(double number); //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * Parses a string using the rule set or DecimalFormat belonging * to this substitution. If there's a match, a mathematical * operation (the inverse of the one used in formatting) is * performed on the result of the parse and the value passed in * and returned as the result. The parse position is updated to * point to the first unmatched character in the string. * @param text The string to parse * @param parsePosition On entry, ignored, but assumed to be 0. * On exit, this is updated to point to the first unmatched * character (or 0 if the substitution didn't match) * @param baseValue A partial parse result that should be * combined with the result of this parse * @param upperBound When searching the rule set for a rule * matching the string passed in, only rules with base values * lower than this are considered * @param lenientParse If true and matching against rules fails, * the substitution will also try matching the text against * numerals using a default-constructed NumberFormat. If false, * no extra work is done. (This value is false whenever the * formatter isn't in lenient-parse mode, but is also false * under some conditions even when the formatter _is_ in * lenient-parse mode.) * @return If there's a match, this is the result of composing * baseValue with whatever was returned from matching the * characters. This will be either a Long or a Double. If there's * no match this is Long.valueOf(0) (not null), and parsePosition * is left unchanged. */ public Number doParse(String text, ParsePosition parsePosition, double baseValue, double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { Number tempResult; // figure out the highest base value a rule can have and match // the text being parsed (this varies according to the type of // substitutions: multiplier, modulus, and numerator substitutions // restrict the search to rules with base values lower than their // own; same-value substitutions leave the upper bound wherever // it was, and the others allow any rule to match upperBound = calcUpperBound(upperBound); // use our rule set to parse the text. If that fails and // lenient parsing is enabled (this is always false if the // formatter's lenient-parsing mode is off, but it may also // be false even when the formatter's lenient-parse mode is // on), then also try parsing the text using a default- // constructed NumberFormat if (ruleSet != null) { tempResult = ruleSet.parse(text, parsePosition, upperBound, nonNumericalExecutedRuleMask); if (lenientParse && !ruleSet.isFractionSet() && parsePosition.getIndex() == 0) { tempResult = ruleSet.owner.getDecimalFormat().parse(text, parsePosition); } // ...or use our DecimalFormat to parse the text } else { tempResult = numberFormat.parse(text, parsePosition); } // if the parse was successful, we've already advanced the caller's // parse position (this is the one function that doesn't have one // of its own). Derive a parse result and return it as a Long, // if possible, or a Double if (parsePosition.getIndex() != 0) { double result = tempResult.doubleValue(); // composeRuleValue() produces a full parse result from // the partial parse result passed to this function from // the caller (this is either the owning rule's base value // or the partial result obtained from composing the // owning rule's base value with its other substitution's // parse result) and the partial parse result obtained by // matching the substitution (which will be the same value // the caller would get by parsing just this part of the // text with RuleBasedNumberFormat.parse() ). How the two // values are used to derive the full parse result depends // on the types of substitutions: For a regular rule, the // ultimate result is its multiplier substitution's result // times the rule's divisor (or the rule's base value) plus // the modulus substitution's result (which will actually // supersede part of the rule's base value). For a negative- // number rule, the result is the negative of its substitution's // result. For a fraction rule, it's the sum of its two // substitution results. For a rule in a fraction rule set, // it's the numerator substitution's result divided by // the rule's base value. Results from same-value substitutions // propagate back upward, and null substitutions don't affect // the result. result = composeRuleValue(result, baseValue); if (result == (long)result) { return (long) result; } else { return result; } // if the parse was UNsuccessful, return 0 } else { return tempResult; } } /** * Derives a new value from the two values passed in. The two values * are typically either the base values of two rules (the one containing * the substitution and the one matching the substitution) or partial * parse results derived in some other way. The operation is generally * the inverse of the operation performed by transformNumber(). * @param newRuleValue The value produced by matching this substitution * @param oldRuleValue The value that was passed to the substitution * by the rule that owns it * @return A third value derived from the other two, representing a * partial parse result */ public abstract double composeRuleValue(double newRuleValue, double oldRuleValue); /** * Calculates an upper bound when searching for a rule that matches * this substitution. Rules with base values greater than or equal * to upperBound are not considered. * @param oldUpperBound The current upper-bound setting. The new * upper bound can't be any higher. */ public abstract double calcUpperBound(double oldUpperBound); //----------------------------------------------------------------------- // simple accessors //----------------------------------------------------------------------- /** * Returns the substitution's position in the rule that owns it. * @return The substitution's position in the rule that owns it. */ public final int getPos() { return pos; } /** * Returns the character used in the textual representation of * substitutions of this type. Used by toString(). * @return This substitution's token character. */ abstract char tokenChar(); /** * Returns true if this is a modulus substitution. (We didn't do this * with instanceof partially because it causes source files to * proliferate and partially because we have to port this to C++.) * @return true if this object is an instance of ModulusSubstitution */ public boolean isModulusSubstitution() { return false; } public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { if (numberFormat != null) { numberFormat.setDecimalFormatSymbols(newSymbols); } } } //=================================================================== // SameValueSubstitution //=================================================================== /** * A substitution that passes the value passed to it through unchanged. * Represented by == in rule descriptions. */ class SameValueSubstitution extends NFSubstitution { //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Constructs a SameValueSubstution. This function just uses the * superclass constructor, but it performs a check that this * substitution doesn't call the rule set that owns it, since that * would lead to infinite recursion. */ SameValueSubstitution(int pos, NFRuleSet ruleSet, String description) { super(pos, ruleSet, description); if (description.equals("==")) { throw new IllegalArgumentException("== is not a legal token"); } } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * Returns "number" unchanged. * @return "number" */ @Override public long transformNumber(long number) { return number; } /** * Returns "number" unchanged. * @return "number" */ @Override public double transformNumber(double number) { return number; } //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * Returns newRuleValue and ignores oldRuleValue. (The value we got * matching the substitution supersedes the value of the rule * that owns the substitution.) * @param newRuleValue The value resulting from matching the substitution * @param oldRuleValue The value of the rule containing the * substitution. * @return newRuleValue */ @Override public double composeRuleValue(double newRuleValue, double oldRuleValue) { return newRuleValue; } /** * SameValueSubstitution doesn't change the upper bound. * @param oldUpperBound The current upper bound. * @return oldUpperBound */ @Override public double calcUpperBound(double oldUpperBound) { return oldUpperBound; } //----------------------------------------------------------------------- // simple accessor //----------------------------------------------------------------------- /** * The token character for a SameValueSubstitution is =. * @return '=' */ @Override char tokenChar() { return '='; } } //=================================================================== // MultiplierSubstitution //=================================================================== /** * A substitution that divides the number being formatted by the rule's * divisor and formats the quotient. Represented by << in normal * rules. */ class MultiplierSubstitution extends NFSubstitution { //----------------------------------------------------------------------- // data members //----------------------------------------------------------------------- /** * The divisor of the rule that owns this substitution. */ long divisor; /** * A backpointer to the owning rule. Used in the rounding logic to determine * whether the owning rule also has a modulus substitution. */ NFRule owningRule; //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Constructs a MultiplierSubstitution. This uses the superclass * constructor to initialize most members, but this substitution * also maintains its own copy of its rule's divisor. * @param pos The substitution's position in its rule's rule text * @param rule The rule that owns this substitution * @param ruleSet The ruleSet this substitution uses to format its result * @param description The description describing this substitution */ MultiplierSubstitution(int pos, NFRule rule, NFRuleSet ruleSet, String description) { super(pos, ruleSet, description); // the owning rule's divisor affects the behavior of this // substitution. Rather than keeping a back-pointer to the // rule, we keep a copy of the divisor this.divisor = rule.getDivisor(); this.owningRule = rule; if (divisor == 0) { // this will cause recursion throw new IllegalStateException("Substitution with divisor 0 " + description.substring(0, pos) + " | " + description.substring(pos)); } } /** * Sets the substitution's divisor based on the values passed in. * @param radix The radix of the divisor. * @param exponent The exponent of the divisor. */ @Override public void setDivisor(int radix, short exponent) { divisor = NFRule.power(radix, exponent); if (divisor == 0) { throw new IllegalStateException("Substitution with divisor 0"); } } //----------------------------------------------------------------------- // boilerplate //----------------------------------------------------------------------- /** * Augments the superclass's equals() function by comparing divisors. * @param that The other substitution * @return true if the two substitutions are functionally equal */ @Override public boolean equals(Object that) { return super.equals(that) && divisor == ((MultiplierSubstitution) that).divisor; } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * Divides the number by the rule's divisor and returns the quotient. * @param number The number being formatted. * @return "number" divided by the rule's divisor */ @Override public long transformNumber(long number) { return (long)Math.floor(number / divisor); } /** * Divides the number by the rule's divisor and returns the quotient. * This is an integral quotient if we're filling in the substitution * using another rule set, but it's the full quotient (integral and * fractional parts) if we're filling in the substitution using * a DecimalFormat. (This allows things such as "1.2 million".) * @param number The number being formatted * @return "number" divided by the rule's divisor */ @Override public double transformNumber(double number) { // Most of the time, when a number is handled by an NFSubstitution, we do a floor() on it, but // if a substitution uses a DecimalFormat to format the number instead of a ruleset, we generally // don't want to do a floor()-- we want to keep the value intact so that the DecimalFormat can // either include the fractional part or round properly. The big exception to this is here in // MultiplierSubstitution. If the rule includes two substitutions, the MultiplierSubstitution // (which is handling the larger part of the number) really _does_ want to do a floor(), because // the ModulusSubstitution (which is handling the smaller part of the number) will take // care of the fractional part. (Consider something like `1/12: <0< feet >0.0> inches;`.) // But if there is no ModulusSubstitution, we're shortening the number in some way-- the "larger part" // of the number is the only part we're keeping. Even if the DecimalFormat doesn't include the // fractional part in its output, we still want it to round. (Consider something like `1/1000: <0>> token doesn't alter how this substitution calculates the // values it uses for formatting and parsing, but it changes // what's done with that value after it's obtained: >>> short- // circuits the rule-search process and goes straight to the // specified rule to format the substitution value if (description.equals(">>>")) { ruleToUse = rulePredecessor; } else { ruleToUse = null; } } /** * Makes the substitution's divisor conform to that of the rule * that owns it. Used when the divisor is determined after creation. * @param radix The radix of the divisor. * @param exponent The exponent of the divisor. */ @Override public void setDivisor(int radix, short exponent) { divisor = NFRule.power(radix, exponent); if (divisor == 0) { // this will cause recursion throw new IllegalStateException("Substitution with bad divisor"); } } //----------------------------------------------------------------------- // boilerplate //----------------------------------------------------------------------- /** * Augments the inherited equals() function by comparing divisors and * ruleToUse. * @param that The other substitution * @return true if the two substitutions are functionally equivalent */ @Override public boolean equals(Object that) { if (super.equals(that)) { ModulusSubstitution that2 = (ModulusSubstitution)that; return divisor == that2.divisor; } else { return false; } } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * If this is a >>> substitution, use ruleToUse to fill in * the substitution. Otherwise, just use the superclass function. * @param number The number being formatted * @param toInsertInto The string to insert the result of this substitution * into * @param position The position of the rule text in toInsertInto */ @Override public void doSubstitution(long number, StringBuilder toInsertInto, int position, int recursionCount) { // if this isn't a >>> substitution, just use the inherited version // of this function (which uses either a rule set or a DecimalFormat // to format its substitution value) if (ruleToUse == null) { super.doSubstitution(number, toInsertInto, position, recursionCount); } else { // a >>> substitution goes straight to a particular rule to // format the substitution value long numberToFormat = transformNumber(number); ruleToUse.doFormat(numberToFormat, toInsertInto, position + pos, recursionCount); } } /** * If this is a >>> substitution, use ruleToUse to fill in * the substitution. Otherwise, just use the superclass function. * @param number The number being formatted * @param toInsertInto The string to insert the result of this substitution * into * @param position The position of the rule text in toInsertInto */ @Override public void doSubstitution(double number, StringBuilder toInsertInto, int position, int recursionCount) { // if this isn't a >>> substitution, just use the inherited version // of this function (which uses either a rule set or a DecimalFormat // to format its substitution value) if (ruleToUse == null) { super.doSubstitution(number, toInsertInto, position, recursionCount); } else { // a >>> substitution goes straight to a particular rule to // format the substitution value double numberToFormat = transformNumber(number); ruleToUse.doFormat(numberToFormat, toInsertInto, position + pos, recursionCount); } } /** * Divides the number being formatted by the rule's divisor and * returns the remainder. * @param number The number being formatted * @return "number" mod divisor */ @Override public long transformNumber(long number) { return number % divisor; } /** * Divides the number being formatted by the rule's divisor and * returns the remainder. * @param number The number being formatted * @return "number" mod divisor */ @Override public double transformNumber(double number) { return number % divisor; } //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * If this is a >>> substitution, match only against ruleToUse. * Otherwise, use the superclass function. * @param text The string to parse * @param parsePosition Ignored on entry, updated on exit to point to * the first unmatched character. * @param baseValue The partial parse result prior to calling this * routine. */ @Override public Number doParse(String text, ParsePosition parsePosition, double baseValue, double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { // if this isn't a >>> substitution, we can just use the // inherited parse() routine to do the parsing if (ruleToUse == null) { return super.doParse(text, parsePosition, baseValue, upperBound, lenientParse, nonNumericalExecutedRuleMask); } else { // but if it IS a >>> substitution, we have to do it here: we // use the specific rule's doParse() method, and then we have to // do some of the other work of NFRuleSet.parse() Number tempResult = ruleToUse.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask); if (parsePosition.getIndex() != 0) { double result = tempResult.doubleValue(); result = composeRuleValue(result, baseValue); if (result == (long)result) { return (long) result; } else { return result; } } else { return tempResult; } } } /** * Returns the highest multiple of the rule's divisor that its less * than or equal to oldRuleValue, plus newRuleValue. (The result * is the sum of the result of parsing the substitution plus the * base value of the rule containing the substitution, but if the * owning rule's base value isn't an even multiple of its divisor, * we have to round it down to a multiple of the divisor, or we * get unwanted digits in the result.) * @param newRuleValue The result of parsing the substitution * @param oldRuleValue The base value of the rule containing the * substitution */ @Override public double composeRuleValue(double newRuleValue, double oldRuleValue) { return (oldRuleValue - (oldRuleValue % divisor)) + newRuleValue; } /** * Sets the upper bound down to the owning rule's divisor * @param oldUpperBound Ignored * @return The owning rule's divisor */ @Override public double calcUpperBound(double oldUpperBound) { return divisor; } //----------------------------------------------------------------------- // simple accessors //----------------------------------------------------------------------- /** * Returns true. This _is_ a ModulusSubstitution. * @return true */ @Override public boolean isModulusSubstitution() { return true; } /** * The token character of a ModulusSubstitution is >. * @return '>' */ @Override char tokenChar() { return '>'; } } //=================================================================== // IntegralPartSubstitution //=================================================================== /** * A substitution that formats the number's integral part. This is * represented by << in a fraction rule. */ class IntegralPartSubstitution extends NFSubstitution { //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Constructs an IntegralPartSubstitution. This just calls * the superclass constructor. */ IntegralPartSubstitution(int pos, NFRuleSet ruleSet, String description) { super(pos, ruleSet, description); } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * Returns the number's integral part. (For a long, that's just the * number unchanged.) * @param number The number being formatted * @return "number" unchanged */ @Override public long transformNumber(long number) { return number; } /** * Returns the number's integral part. * @param number The integral part of the number being formatted * @return floor(number) */ @Override public double transformNumber(double number) { return Math.floor(number); } //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * Returns the sum of the result of parsing the substitution and the * owning rule's base value. (The owning rule, at best, has an * integral-part substitution and a fractional-part substitution, * so we can safely just add them.) * @param newRuleValue The result of matching the substitution * @param oldRuleValue The partial result of the parse prior to * calling this function * @return oldRuleValue + newRuleValue */ @Override public double composeRuleValue(double newRuleValue, double oldRuleValue) { return newRuleValue + oldRuleValue; } /** * An IntegralPartSubstitution sets the upper bound back up so all * potentially matching rules are considered. * @param oldUpperBound Ignored * @return Double.MAX_VALUE */ @Override public double calcUpperBound(double oldUpperBound) { return Double.MAX_VALUE; } //----------------------------------------------------------------------- // simple accessor //----------------------------------------------------------------------- /** * An IntegralPartSubstitution's token character is < * @return '<' */ @Override char tokenChar() { return '<'; } } //=================================================================== // FractionalPartSubstitution //=================================================================== /** * A substitution that formats the fractional part of a number. This is * represented by >> in a fraction rule. */ class FractionalPartSubstitution extends NFSubstitution { //----------------------------------------------------------------------- // data members //----------------------------------------------------------------------- /** * true if this substitution should have the default "by digits" * behavior, false otherwise */ private final boolean byDigits; /** * true if we automatically insert spaces to separate names of digits * set to false by '>>>' in fraction rules, used by Thai. */ private final boolean useSpaces; //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Constructs a FractionalPartSubstitution. This object keeps a flag * telling whether it should format by digits or not. In addition, * it marks the rule set it calls (if any) as a fraction rule set. */ FractionalPartSubstitution(int pos, NFRuleSet ruleSet, String description) { super(pos, ruleSet, description); if (description.equals(">>") || description.equals(">>>") || ruleSet == this.ruleSet) { byDigits = true; useSpaces = !description.equals(">>>"); } else { byDigits = false; useSpaces = true; this.ruleSet.makeIntoFractionRuleSet(); } } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * If in "by digits" mode, fills in the substitution one decimal digit * at a time using the rule set containing this substitution. * Otherwise, uses the superclass function. * @param number The number being formatted * @param toInsertInto The string to insert the result of formatting * the substitution into * @param position The position of the owning rule's rule text in * toInsertInto */ @Override public void doSubstitution(double number, StringBuilder toInsertInto, int position, int recursionCount) { if (!byDigits) { // if we're not in "byDigits" mode, just use the inherited // doSubstitution() routine super.doSubstitution(number, toInsertInto, position, recursionCount); } else { // if we're in "byDigits" mode, transform the value into an integer // by moving the decimal point eight places to the right and // pulling digits off the right one at a time, formatting each digit // as an integer using this substitution's owning rule set // (this is slower, but more accurate, than doing it from the // other end) DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(number); fq.roundToInfinity(); // ensure doubles are resolved using slow path boolean pad = false; int mag = fq.getLowerDisplayMagnitude(); while (mag < 0) { if (pad && useSpaces) { toInsertInto.insert(position + pos, ' '); } else { pad = true; } ruleSet.format(fq.getDigit(mag++), toInsertInto, position + pos, recursionCount); } } } /** * Returns the fractional part of the number, which will always be * zero if it's a long. * @param number The number being formatted * @return 0 */ @Override public long transformNumber(long number) { return 0; } /** * Returns the fractional part of the number. * @param number The number being formatted. * @return number - floor(number) */ @Override public double transformNumber(double number) { return number - Math.floor(number); } //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * If in "by digits" mode, parses the string as if it were a string * of individual digits; otherwise, uses the superclass function. * @param text The string to parse * @param parsePosition Ignored on entry, but updated on exit to point * to the first unmatched character * @param baseValue The partial parse result prior to entering this * function * @param upperBound Only consider rules with base values lower than * this when filling in the substitution * @param lenientParse If true, try matching the text as numerals if * matching as words doesn't work * @return If the match was successful, the current partial parse * result; otherwise Long.valueOf(0). The result is either a Long or * a Double. */ @Override public Number doParse(String text, ParsePosition parsePosition, double baseValue, double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { // if we're not in byDigits mode, we can just use the inherited // doParse() if (!byDigits) { return super.doParse(text, parsePosition, baseValue, 0, lenientParse, nonNumericalExecutedRuleMask); } else { // if we ARE in byDigits mode, parse the text one digit at a time // using this substitution's owning rule set (we do this by setting // upperBound to 10 when calling doParse() ) until we reach // nonmatching text String workText = text; ParsePosition workPos = new ParsePosition(1); double result; int digit; DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(); int totalDigits = 0; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); digit = ruleSet.parse(workText, workPos, 10, nonNumericalExecutedRuleMask).intValue(); if (lenientParse && workPos.getIndex() == 0) { Number n = ruleSet.owner.getDecimalFormat().parse(workText, workPos); if (n != null) { digit = n.intValue(); } } if (workPos.getIndex() != 0) { fq.appendDigit((byte) digit, 0, true); totalDigits++; parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex()); workText = workText.substring(workPos.getIndex()); while (workText.length() > 0 && workText.charAt(0) == ' ') { workText = workText.substring(1); parsePosition.setIndex(parsePosition.getIndex() + 1); } } } fq.adjustMagnitude(-totalDigits); result = fq.toDouble(); result = composeRuleValue(result, baseValue); return result; } } /** * Returns the sum of the two partial parse results. * @param newRuleValue The result of parsing the substitution * @param oldRuleValue The partial parse result prior to calling * this function * @return newRuleValue + oldRuleValue */ @Override public double composeRuleValue(double newRuleValue, double oldRuleValue) { return newRuleValue + oldRuleValue; } /** * Not used. */ @Override public double calcUpperBound(double oldUpperBound) { return 0; // this value is ignored } //----------------------------------------------------------------------- // simple accessor //----------------------------------------------------------------------- /** * The token character for a FractionalPartSubstitution is >. * @return '>' */ @Override char tokenChar() { return '>'; } } //=================================================================== // AbsoluteValueSubstitution //=================================================================== /** * A substitution that formats the absolute value of the number. * This substitution is represented by >> in a negative-number rule. */ class AbsoluteValueSubstitution extends NFSubstitution { //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Constructs an AbsoluteValueSubstitution. This just uses the * superclass constructor. */ AbsoluteValueSubstitution(int pos, NFRuleSet ruleSet, String description) { super(pos, ruleSet, description); } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * Returns the absolute value of the number. * @param number The number being formatted. * @return abs(number) */ @Override public long transformNumber(long number) { return Math.abs(number); } /** * Returns the absolute value of the number. * @param number The number being formatted. * @return abs(number) */ @Override public double transformNumber(double number) { return Math.abs(number); } //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * Returns the additive inverse of the result of parsing the * substitution (this supersedes the earlier partial result) * @param newRuleValue The result of parsing the substitution * @param oldRuleValue The partial parse result prior to calling * this function * @return -newRuleValue */ @Override public double composeRuleValue(double newRuleValue, double oldRuleValue) { return -newRuleValue; } /** * Sets the upper bound beck up to consider all rules * @param oldUpperBound Ignored. * @return Double.MAX_VALUE */ @Override public double calcUpperBound(double oldUpperBound) { return Double.MAX_VALUE; } //----------------------------------------------------------------------- // simple accessor //----------------------------------------------------------------------- /** * The token character for an AbsoluteValueSubstitution is > * @return '>' */ @Override char tokenChar() { return '>'; } } //=================================================================== // NumeratorSubstitution //=================================================================== /** * A substitution that multiplies the number being formatted (which is * between 0 and 1) by the base value of the rule that owns it and * formats the result. It is represented by << in the rules * in a fraction rule set. */ class NumeratorSubstitution extends NFSubstitution { //----------------------------------------------------------------------- // data members //----------------------------------------------------------------------- /** * The denominator of the fraction we're finding the numerator for. * (The base value of the rule that owns this substitution.) */ private final double denominator; /** * True if we format leading zeros (this is a hack for Hebrew spellout) */ private final boolean withZeros; //----------------------------------------------------------------------- // construction //----------------------------------------------------------------------- /** * Constructs a NumeratorSubstitution. In addition to the inherited * fields, a NumeratorSubstitution keeps track of a denominator, which * is merely the base value of the rule that owns it. */ NumeratorSubstitution(int pos, double denominator, NFRuleSet ruleSet, String description) { super(pos, ruleSet, fixdesc(description)); // this substitution's behavior depends on the rule's base value // Rather than keeping a backpointer to the rule, we copy its // base value here this.denominator = denominator; this.withZeros = description.endsWith("<<"); } static String fixdesc(String description) { return description.endsWith("<<") ? description.substring(0,description.length()-1) : description; } //----------------------------------------------------------------------- // boilerplate //----------------------------------------------------------------------- /** * Tests two NumeratorSubstitutions for equality * @param that The other NumeratorSubstitution * @return true if the two objects are functionally equivalent */ @Override public boolean equals(Object that) { if (super.equals(that)) { NumeratorSubstitution that2 = (NumeratorSubstitution)that; return denominator == that2.denominator && withZeros == that2.withZeros; } else { return false; } } //----------------------------------------------------------------------- // formatting //----------------------------------------------------------------------- /** * Performs a mathematical operation on the number, formats it using * either ruleSet or decimalFormat, and inserts the result into * toInsertInto. * @param number The number being formatted. * @param toInsertInto The string we insert the result into * @param position The position in toInsertInto where the owning rule's * rule text begins (this value is added to this substitution's * position to determine exactly where to insert the new text) */ @Override public void doSubstitution(double number, StringBuilder toInsertInto, int position, int recursionCount) { // perform a transformation on the number being formatted that // is dependent on the type of substitution this is //String s = toInsertInto.toString(); double numberToFormat = transformNumber(number); if (withZeros && ruleSet != null) { // if there are leading zeros in the decimal expansion then emit them long nf = (long)numberToFormat; int len = toInsertInto.length(); while ((nf *= 10) < denominator) { toInsertInto.insert(position + pos, ' '); ruleSet.format(0, toInsertInto, position + pos, recursionCount); } position += toInsertInto.length() - len; } // if the result is an integer, from here on out we work in integer // space (saving time and memory and preserving accuracy) if (numberToFormat == Math.floor(numberToFormat) && ruleSet != null) { ruleSet.format((long)numberToFormat, toInsertInto, position + pos, recursionCount); // if the result isn't an integer, then call either our rule set's // format() method or our DecimalFormat's format() method to // format the result } else { if (ruleSet != null) { ruleSet.format(numberToFormat, toInsertInto, position + pos, recursionCount); } else { toInsertInto.insert(position + pos, numberFormat.format(numberToFormat)); } } } /** * Returns the number being formatted times the denominator. * @param number The number being formatted * @return number * denominator */ @Override public long transformNumber(long number) { return Math.round(number * denominator); } /** * Returns the number being formatted times the denominator. * @param number The number being formatted * @return number * denominator */ @Override public double transformNumber(double number) { return Math.round(number * denominator); } //----------------------------------------------------------------------- // parsing //----------------------------------------------------------------------- /** * Dispatches to the inherited version of this function, but makes * sure that lenientParse is off. */ @Override public Number doParse(String text, ParsePosition parsePosition, double baseValue, double upperBound, boolean lenientParse, int nonNumericalExecutedRuleMask) { // we don't have to do anything special to do the parsing here, // but we have to turn lenient parsing off-- if we leave it on, // it SERIOUSLY messes up the algorithm // if withZeros is true, we need to count the zeros // and use that to adjust the parse result int zeroCount = 0; if (withZeros) { String workText = text; ParsePosition workPos = new ParsePosition(1); //int digit; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); /*digit = */ruleSet.parse(workText, workPos, 1, nonNumericalExecutedRuleMask).intValue(); // parse zero or nothing at all if (workPos.getIndex() == 0) { // we failed, either there were no more zeros, or the number was formatted with digits // either way, we're done break; } ++zeroCount; parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex()); workText = workText.substring(workPos.getIndex()); while (workText.length() > 0 && workText.charAt(0) == ' ') { workText = workText.substring(1); parsePosition.setIndex(parsePosition.getIndex() + 1); } } text = text.substring(parsePosition.getIndex()); // arrgh! parsePosition.setIndex(0); } // we've parsed off the zeros, now let's parse the rest from our current position Number result = super.doParse(text, parsePosition, withZeros ? 1 : baseValue, upperBound, false, nonNumericalExecutedRuleMask); if (withZeros) { // any base value will do in this case. is there a way to // force this to not bother trying all the base values? // compute the 'effective' base and prescale the value down long n = result.longValue(); long d = 1; while (d <= n) { d *= 10; } // now add the zeros while (zeroCount > 0) { d *= 10; --zeroCount; } // d is now our true denominator result = (double)n/(double)d; } return result; } /** * Divides the result of parsing the substitution by the partial * parse result. * @param newRuleValue The result of parsing the substitution * @param oldRuleValue The owning rule's base value * @return newRuleValue / oldRuleValue */ @Override public double composeRuleValue(double newRuleValue, double oldRuleValue) { return newRuleValue / oldRuleValue; } /** * Sets the upper bound down to this rule's base value * @param oldUpperBound Ignored * @return The base value of the rule owning this substitution */ @Override public double calcUpperBound(double oldUpperBound) { return denominator; } //----------------------------------------------------------------------- // simple accessor //----------------------------------------------------------------------- /** * The token character for a NumeratorSubstitution is < * @return '<' */ @Override char tokenChar() { return '<'; } }