com.ibm.icu.text.NFSubstitution Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 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 com.ibm.icu.text;
import java.text.ParsePosition;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
//===================================================================
// 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 (preserving
// the result's fractional part if the formatter it set
// to show it), then use that formatter's format() method
// to format the result
double numberToFormat = transformNumber((double) number);
if (numberFormat.getMaximumFractionDigits() == 0) {
numberToFormat = Math.floor(numberToFormat);
}
toInsertInto.insert(position + pos, numberFormat.format(numberToFormat));
}
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 new Long(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.valueOf((long)result);
} else {
return new Double(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;
//-----------------------------------------------------------------------
// 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();
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) {
boolean doFloor = ruleSet != null;
if (!doFloor) {
// This is a HACK that partially addresses ICU-22313. The original code wanted us to do
// floor() on the result if we were passing it to another rule set, but not if we were passing
// it to a DecimalFormat. But the DurationRules rule set has multiplier substitutions where
// we DO want to do the floor() operation. What we REALLY want is to do floor() any time
// the owning rule also has a ModulusSubsitution, but we don't have access to that information
// here, so instead we're doing a floor() any time the DecimalFormat has maxFracDigits equal to
// 0. This seems to work with our existing rule sets, but could be a problem in the future,
// but the "real" fix for DurationRules isn't worth doing, since we're deprecating DurationRules
// anyway. This is enough to keep it from being egregiously wrong, without obvious side
// effects. --rtg 8/16/23
if (numberFormat == null || numberFormat.getMaximumFractionDigits() == 0) {
doFloor = true;
}
}
if (!doFloor) {
return number / divisor;
} else {
return Math.floor(number / divisor);
}
}
//-----------------------------------------------------------------------
// parsing
//-----------------------------------------------------------------------
/**
* Returns newRuleValue times the divisor. Ignores oldRuleValue.
* (The result of matching a << substitution supersedes the base
* value of the rule that contains it.)
* @param newRuleValue The result of matching the substitution
* @param oldRuleValue The base value of the rule containing the
* substitution
* @return newRuleValue * divisor
*/
@Override
public double composeRuleValue(double newRuleValue, double oldRuleValue) {
return newRuleValue * divisor;
}
/**
* Sets the upper bound down to the rule's divisor.
* @param oldUpperBound Ignored.
* @return The rule's divisor.
*/
@Override
public double calcUpperBound(double oldUpperBound) {
return divisor;
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* The token character for a multiplier substitution is <.
* @return '<'
*/
@Override
char tokenChar() {
return '<';
}
}
//===================================================================
// ModulusSubstitution
//===================================================================
/**
* A substitution that divides the number being formatted by the its rule's
* divisor and formats the remainder. Represented by ">>" in a
* regular rule.
*/
class ModulusSubstitution extends NFSubstitution {
//-----------------------------------------------------------------------
// data members
//-----------------------------------------------------------------------
/**
* The divisor of the rule owning this substitution
*/
long divisor;
/**
* If this is a >>> substitution, the rule to use to format
* the substitution value. Otherwise, null.
*/
private final NFRule ruleToUse;
//-----------------------------------------------------------------------
// construction
//-----------------------------------------------------------------------
/**
* Constructs a ModulusSubstitution. In addition to the inherited
* members, a ModulusSubstitution keeps track of the divisor of the
* rule that owns it, and may also keep a reference to the rule
* that precedes the rule containing this substitution in the rule
* set's rule list.
* @param pos The substitution's position in its rule's rule text
* @param rule The rule that owns this substitution
* @param rulePredecessor The rule that precedes this substitution's
* rule in its rule set's rule list
* @param description The description for this substitution
*/
ModulusSubstitution(int pos,
NFRule rule,
NFRule rulePredecessor,
NFRuleSet ruleSet,
String description)
{
super(pos, ruleSet, description);
// the owning rule's divisor controls the behavior of this
// substitution: rather than keeping a backpointer to the rule,
// we keep a copy of the divisor
this.divisor = rule.getDivisor();
if (divisor == 0) { // this will cause recursion
throw new IllegalStateException("Substitution with bad divisor (" + divisor + ") "+ description.substring(0, pos) +
" | " + description.substring(pos));
}
// the >>> 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.valueOf((long)result);
} else {
return new Double(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 new Long(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 new Double(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 = new 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 '<';
}
}