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 virtdata-lib-realer Show documentation
Show all versions of virtdata-lib-realer Show documentation
With inspiration from other libraries
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
*******************************************************************************
* Copyright (C) 1996-2015, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
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.MASTER_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.MASTER_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
*/
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;
}
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.
*/
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
//-----------------------------------------------------------------------
/**
* 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) {
// 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);
if (ruleSet != null) {
ruleSet.format(numberToFormat, toInsertInto, position + pos, recursionCount);
} else {
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) {
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);
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"
*/
public long transformNumber(long number) {
return number;
}
/**
* Returns "number" unchanged.
* @return "number"
*/
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
*/
public double composeRuleValue(double newRuleValue, double oldRuleValue) {
return newRuleValue;
}
/**
* SameValueSubstitution doesn't change the upper bound.
* @param oldUpperBound The current upper bound.
* @return oldUpperBound
*/
public double calcUpperBound(double oldUpperBound) {
return oldUpperBound;
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* The token character for a SameValueSubstitution is =.
* @return '='
*/
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.
*/
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
*/
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
*/
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
*/
public double transformNumber(double number) {
if (ruleSet == null) {
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
*/
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.
*/
public double calcUpperBound(double oldUpperBound) {
return divisor;
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* The token character for a multiplier substitution is <.
* @return '<'
*/
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.
*/
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
*/
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
*/
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
*/
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
*/
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
*/
public double transformNumber(double number) {
return Math.floor(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.
*/
public Number doParse(String text, ParsePosition parsePosition, double baseValue,
double upperBound, boolean lenientParse) {
// 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);
} 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);
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
*/
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
*/
public double calcUpperBound(double oldUpperBound) {
return divisor;
}
//-----------------------------------------------------------------------
// simple accessors
//-----------------------------------------------------------------------
/**
* Returns true. This _is_ a ModulusSubstitution.
* @return true
*/
public boolean isModulusSubstitution() {
return true;
}
/**
* The token character of a ModulusSubstitution is >.
* @return '>'
*/
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
*/
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)
*/
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
*/
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
*/
public double calcUpperBound(double oldUpperBound) {
return Double.MAX_VALUE;
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* An IntegralPartSubstitution's token character is <
* @return '<'
*/
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
*/
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)
// just print to string and then use that
DigitList dl = new DigitList();
dl.set(number, 20, true);
boolean pad = false;
while (dl.count > Math.max(0, dl.decimalAt)) {
if (pad && useSpaces) {
toInsertInto.insert(position + pos, ' ');
} else {
pad = true;
}
ruleSet.format(dl.digits[--dl.count] - '0', toInsertInto, position + pos, recursionCount);
}
while (dl.decimalAt < 0) {
if (pad && useSpaces) {
toInsertInto.insert(position + pos, ' ');
} else {
pad = true;
}
ruleSet.format(0, toInsertInto, position + pos, recursionCount);
++dl.decimalAt;
}
}
}
/**
* 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
*/
public long transformNumber(long number) {
return 0;
}
/**
* Returns the fractional part of the number.
* @param number The number being formatted.
* @return number - floor(number)
*/
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.
*/
public Number doParse(String text, ParsePosition parsePosition, double baseValue,
double upperBound, boolean lenientParse) {
// if we're not in byDigits mode, we can just use the inherited
// doParse()
if (!byDigits) {
return super.doParse(text, parsePosition, baseValue, 0, lenientParse);
}
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;
DigitList dl = new DigitList();
while (workText.length() > 0 && workPos.getIndex() != 0) {
workPos.setIndex(0);
digit = ruleSet.parse(workText, workPos, 10).intValue();
if (lenientParse && workPos.getIndex() == 0) {
Number n = ruleSet.owner.getDecimalFormat().parse(workText, workPos);
if (n != null) {
digit = n.intValue();
}
}
if (workPos.getIndex() != 0) {
dl.append('0'+digit);
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);
}
}
}
result = dl.count == 0 ? 0 : dl.getDouble();
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
*/
public double composeRuleValue(double newRuleValue, double oldRuleValue) {
return newRuleValue + oldRuleValue;
}
/**
* Not used.
*/
public double calcUpperBound(double oldUpperBound) {
return 0; // this value is ignored
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* The token character for a FractionalPartSubstitution is >.
* @return '>'
*/
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)
*/
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)
*/
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
*/
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
*/
public double calcUpperBound(double oldUpperBound) {
return Double.MAX_VALUE;
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* The token character for an AbsoluteValueSubstitution is >
* @return '>'
*/
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
*/
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)
*/
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
*/
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
*/
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.
*/
public Number doParse(String text, ParsePosition parsePosition, double baseValue,
double upperBound, boolean lenientParse) {
// 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).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);
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
*/
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
*/
public double calcUpperBound(double oldUpperBound) {
return denominator;
}
//-----------------------------------------------------------------------
// simple accessor
//-----------------------------------------------------------------------
/**
* The token character for a NumeratorSubstitution is <
* @return '<'
*/
char tokenChar() {
return '<';
}
}