NFSubstitution.java
// © 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 com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.math.BigDecimal;
import java.text.ParsePosition;
// ===================================================================
// 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 = 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,
int recursionCount) {
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,
recursionCount);
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<K;`.)
// (TODO: The ROUND_FLOOR thing is a kludge to preserve the previous floor-always behavior.
// What we probably really want to do is just set the rounding mode on the DecimalFormat
// to match the rounding mode on the RuleBasedNumberFormat and then pass the number to it
// whole and let it do its own rounding. But before making that change, we'd have to make
// sure it didn't have undesirable side effects.)
if (ruleSet != null
|| owningRule.hasModulusSubstitution()
|| owningRule.formatter.getRoundingMode() == BigDecimal.ROUND_FLOOR) {
return Math.floor(number / divisor);
} else {
return 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,
int recursionCount) {
// 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,
recursionCount);
} 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,
recursionCount);
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;
if (this.ruleSet != null) {
this.ruleSet.makeIntoFractionRuleSet();
} else {
throw new IllegalArgumentException("rule set is missing");
}
}
}
// -----------------------------------------------------------------------
// 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,
int recursionCount) {
// 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,
recursionCount);
} 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,
recursionCount)
.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,
int recursionCount) {
// 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);
while (workText.length() > 0 && workPos.getIndex() != 0) {
workPos.setIndex(0);
var unused =
ruleSet.parse(
workText,
workPos,
1,
nonNumericalExecutedRuleMask,
recursionCount)
.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,
recursionCount);
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 '<';
}
}