com.ibm.icu.text.NFRuleSet Maven / Gradle / Ivy
The newest version!
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 1996-2015, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import com.ibm.icu.impl.PatternProps;
/**
* A collection of rules used by a RuleBasedNumberFormat to format and
* parse numbers. It is the responsibility of a RuleSet to select an
* appropriate rule for formatting a particular number and dispatch
* control to it, and to arbitrate between different rules when parsing
* a number.
*/
final class NFRuleSet {
//-----------------------------------------------------------------------
// data members
//-----------------------------------------------------------------------
/**
* The rule set's name
*/
private final String name;
/**
* The rule set's regular rules
*/
private NFRule[] rules;
/**
* The rule set's non-numerical rules like negative, fractions, infinity and NaN
*/
final NFRule[] nonNumericalRules = new NFRule[6];
/**
* These are a pile of fraction rules in declared order. They may have alternate
* ways to represent fractions.
*/
LinkedList fractionRules;
/** -x */
static final int NEGATIVE_RULE_INDEX = 0;
/** x.x */
static final int IMPROPER_FRACTION_RULE_INDEX = 1;
/** 0.x */
static final int PROPER_FRACTION_RULE_INDEX = 2;
/** x.0 */
static final int DEFAULT_RULE_INDEX = 3;
/** Inf */
static final int INFINITY_RULE_INDEX = 4;
/** NaN */
static final int NAN_RULE_INDEX = 5;
/**
* The RuleBasedNumberFormat that owns this rule
*/
final RuleBasedNumberFormat owner;
/**
* True if the rule set is a fraction rule set. A fraction rule set
* is a rule set that is used to format the fractional part of a
* number. It is called from a >> substitution in another rule set's
* fraction rule, and is only called upon to format values between
* 0 and 1. A fraction rule set has different rule-selection
* behavior than a regular rule set.
*/
private boolean isFractionRuleSet = false;
/**
* True if the rule set is parseable.
*/
private final boolean isParseable;
/**
* Limit of recursion. It's about a 64 bit number formatted in base 2.
*/
private static final int RECURSION_LIMIT = 64;
//-----------------------------------------------------------------------
// construction
//-----------------------------------------------------------------------
/**
* Constructs a rule set.
* @param owner The formatter that owns this rule set
* @param descriptions An array of Strings representing rule set
* descriptions. On exit, this rule set's entry in the array will
* have been stripped of its rule set name and any trailing whitespace.
* @param index The index into "descriptions" of the description
* for the rule to be constructed
*/
public NFRuleSet(RuleBasedNumberFormat owner, String[] descriptions, int index) throws IllegalArgumentException {
this.owner = owner;
String description = descriptions[index];
if (description.length() == 0) {
throw new IllegalArgumentException("Empty rule set description");
}
// if the description begins with a rule set name (the rule set
// name can be omitted in formatter descriptions that consist
// of only one rule set), copy it out into our "name" member
// and delete it from the description
if (description.charAt(0) == '%') {
int pos = description.indexOf(':');
if (pos == -1) {
throw new IllegalArgumentException("Rule set name doesn't end in colon");
}
else {
String name = description.substring(0, pos);
this.isParseable = !name.endsWith("@noparse");
if (!this.isParseable) {
name = name.substring(0,name.length()-8); // Remove the @noparse from the name
}
this.name = name;
//noinspection StatementWithEmptyBody
while (pos < description.length() && PatternProps.isWhiteSpace(description.charAt(++pos))) {
}
description = description.substring(pos);
descriptions[index] = description;
}
}
else {
// if the description doesn't begin with a rule set name, its
// name is "%default"
name = "%default";
isParseable = true;
}
if (description.length() == 0) {
throw new IllegalArgumentException("Empty rule set description");
}
// all of the other members of NFRuleSet are initialized
// by parseRules()
}
/**
* Construct the subordinate data structures used by this object.
* This function is called by the RuleBasedNumberFormat constructor
* after all the rule sets have been created to actually parse
* the description and build rules from it. Since any rule set
* can refer to any other rule set, we have to have created all of
* them before we can create anything else.
* @param description The textual description of this rule set
*/
public void parseRules(String description) {
// (the number of elements in the description list isn't necessarily
// the number of rules-- some descriptions may expend into two rules)
List tempRules = new ArrayList<>();
// we keep track of the rule before the one we're currently working
// on solely to support >>> substitutions
NFRule predecessor = null;
// Iterate through the rules. The rules
// are separated by semicolons (there's no escape facility: ALL
// semicolons are rule delimiters)
int oldP = 0;
int descriptionLen = description.length();
int p;
do {
p = description.indexOf(';', oldP);
if (p < 0) {
p = descriptionLen;
}
// makeRules (a factory method on NFRule) will return either
// a single rule or an array of rules. Either way, add them
// to our rule vector
NFRule.makeRules(description.substring(oldP, p),
this, predecessor, owner, tempRules);
if (!tempRules.isEmpty()) {
predecessor = tempRules.get(tempRules.size() - 1);
}
oldP = p + 1;
}
while (oldP < descriptionLen);
// for rules that didn't specify a base value, their base values
// were initialized to 0. Make another pass through the list and
// set all those rules' base values. We also remove any special
// rules from the list and put them into their own member variables
long defaultBaseValue = 0;
for (NFRule rule : tempRules) {
long baseValue = rule.getBaseValue();
if (baseValue == 0) {
// if the rule's base value is 0, fill in a default
// base value (this will be 1 plus the preceding
// rule's base value for regular rule sets, and the
// same as the preceding rule's base value in fraction
// rule sets)
rule.setBaseValue(defaultBaseValue);
}
else {
// if it's a regular rule that already knows its base value,
// check to make sure the rules are in order, and update
// the default base value for the next rule
if (baseValue < defaultBaseValue) {
throw new IllegalArgumentException("Rules are not in order, base: " +
baseValue + " < " + defaultBaseValue);
}
defaultBaseValue = baseValue;
}
if (!isFractionRuleSet) {
++defaultBaseValue;
}
}
// finally, we can copy the rules from the vector into a
// fixed-length array
rules = new NFRule[tempRules.size()];
tempRules.toArray(rules);
}
/**
* Set one of the non-numerical rules.
* @param rule The rule to set.
*/
void setNonNumericalRule(NFRule rule) {
long baseValue = rule.getBaseValue();
if (baseValue == NFRule.NEGATIVE_NUMBER_RULE) {
nonNumericalRules[NFRuleSet.NEGATIVE_RULE_INDEX] = rule;
}
else if (baseValue == NFRule.IMPROPER_FRACTION_RULE) {
setBestFractionRule(NFRuleSet.IMPROPER_FRACTION_RULE_INDEX, rule, true);
}
else if (baseValue == NFRule.PROPER_FRACTION_RULE) {
setBestFractionRule(NFRuleSet.PROPER_FRACTION_RULE_INDEX, rule, true);
}
else if (baseValue == NFRule.DEFAULT_RULE) {
setBestFractionRule(NFRuleSet.DEFAULT_RULE_INDEX, rule, true);
}
else if (baseValue == NFRule.INFINITY_RULE) {
nonNumericalRules[NFRuleSet.INFINITY_RULE_INDEX] = rule;
}
else if (baseValue == NFRule.NAN_RULE) {
nonNumericalRules[NFRuleSet.NAN_RULE_INDEX] = rule;
}
}
/**
* Determine the best fraction rule to use. Rules matching the decimal point from
* DecimalFormatSymbols become the main set of rules to use.
* @param originalIndex The index into nonNumericalRules
* @param newRule The new rule to consider
* @param rememberRule Should the new rule be added to fractionRules.
*/
private void setBestFractionRule(int originalIndex, NFRule newRule, boolean rememberRule) {
if (rememberRule) {
if (fractionRules == null) {
fractionRules = new LinkedList<>();
}
fractionRules.add(newRule);
}
NFRule bestResult = nonNumericalRules[originalIndex];
if (bestResult == null) {
nonNumericalRules[originalIndex] = newRule;
}
else {
// We have more than one. Which one is better?
DecimalFormatSymbols decimalFormatSymbols = owner.getDecimalFormatSymbols();
if (decimalFormatSymbols.getDecimalSeparator() == newRule.getDecimalPoint()) {
nonNumericalRules[originalIndex] = newRule;
}
// else leave it alone
}
}
/**
* Flags this rule set as a fraction rule set. This function is
* called during the construction process once we know this rule
* set is a fraction rule set. We don't know a rule set is a
* fraction rule set until we see it used somewhere. This function
* is not ad must not be called at any time other than during
* construction of a RuleBasedNumberFormat.
*/
public void makeIntoFractionRuleSet() {
isFractionRuleSet = true;
}
//-----------------------------------------------------------------------
// boilerplate
//-----------------------------------------------------------------------
/**
* Compares two rule sets for equality.
* @param that The other rule set
* @return true if the two rule sets are functionally equivalent.
*/
@Override
public boolean equals(Object that) {
// if different classes, they're not equal
if (!(that instanceof NFRuleSet)) {
return false;
} else {
// otherwise, compare the members one by one...
NFRuleSet that2 = (NFRuleSet)that;
if (!name.equals(that2.name)
|| rules.length != that2.rules.length
|| isFractionRuleSet != that2.isFractionRuleSet)
{
return false;
}
// ...then compare the non-numerical rule lists...
for (int i = 0; i < nonNumericalRules.length; i++) {
if (!Objects.equals(nonNumericalRules[i], that2.nonNumericalRules[i])) {
return false;
}
}
// ...then compare the rule lists...
for (int i = 0; i < rules.length; i++) {
if (!rules[i].equals(that2.rules[i])) {
return false;
}
}
// ...and if we make it here, they're equal
return true;
}
}
@Override
public int hashCode() {
assert false : "hashCode not designed";
return 42;
}
/**
* Builds a textual representation of a rule set.
* @return A textual representation of a rule set. This won't
* necessarily be the same description that the rule set was
* constructed with, but it will produce the same results.
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
// the rule set name goes first...
result.append(name).append(":\n");
// followed by the regular rules...
for (NFRule rule : rules) {
result.append(rule.toString()).append("\n");
}
// followed by the special rules (if they exist)
for (NFRule rule : nonNumericalRules) {
if (rule != null) {
if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.DEFAULT_RULE)
{
for (NFRule fractionRule : fractionRules) {
if (fractionRule.getBaseValue() == rule.getBaseValue()) {
result.append(fractionRule.toString()).append("\n");
}
}
}
else {
result.append(rule.toString()).append("\n");
}
}
}
return result.toString();
}
//-----------------------------------------------------------------------
// simple accessors
//-----------------------------------------------------------------------
/**
* Says whether this rule set is a fraction rule set.
* @return true if this rule is a fraction rule set; false if it isn't
*/
public boolean isFractionSet() {
return isFractionRuleSet;
}
/**
* Returns the rule set's name
* @return The rule set's name
*/
public String getName() {
return name;
}
/**
* Return true if the rule set is public.
* @return true if the rule set is public
*/
public boolean isPublic() {
return !name.startsWith("%%");
}
/**
* Return true if the rule set can be used for parsing.
* @return true if the rule set can be used for parsing.
*/
public boolean isParseable() {
return isParseable;
}
//-----------------------------------------------------------------------
// formatting
//-----------------------------------------------------------------------
/**
* Formats a long. Selects an appropriate rule and dispatches
* control to it.
* @param number The number being formatted
* @param toInsertInto The string where the result is to be placed
* @param pos The position in toInsertInto where the result of
* this operation is to be inserted
*/
public void format(long number, StringBuilder toInsertInto, int pos, int recursionCount) {
if (recursionCount >= RECURSION_LIMIT) {
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
}
NFRule applicableRule = findNormalRule(number);
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
}
/**
* Formats a double. Selects an appropriate rule and dispatches
* control to it.
* @param number The number being formatted
* @param toInsertInto The string where the result is to be placed
* @param pos The position in toInsertInto where the result of
* this operation is to be inserted
*/
public void format(double number, StringBuilder toInsertInto, int pos, int recursionCount) {
if (recursionCount >= RECURSION_LIMIT) {
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
}
NFRule applicableRule = findRule(number);
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
}
/**
* Selects an appropriate rule for formatting the number.
* @param number The number being formatted.
* @return The rule that should be used to format it
*/
NFRule findRule(double number) {
// if this is a fraction rule set, use findFractionRuleSetRule()
if (isFractionRuleSet) {
return findFractionRuleSetRule(number);
}
if (Double.isNaN(number)) {
NFRule rule = nonNumericalRules[NAN_RULE_INDEX];
if (rule == null) {
rule = owner.getDefaultNaNRule();
}
return rule;
}
// if the number is negative, return the negative number rule
// (if there isn't a negative-number rule, we pretend it's a
// positive number)
if (number < 0) {
if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
return nonNumericalRules[NEGATIVE_RULE_INDEX];
} else {
number = -number;
}
}
if (Double.isInfinite(number)) {
NFRule rule = nonNumericalRules[INFINITY_RULE_INDEX];
if (rule == null) {
rule = owner.getDefaultInfinityRule();
}
return rule;
}
// if the number isn't an integer, we use one f the fraction rules...
if (number != Math.floor(number)) {
if (number < 1 && nonNumericalRules[PROPER_FRACTION_RULE_INDEX] != null) {
// if the number is between 0 and 1, return the proper
// fraction rule
return nonNumericalRules[PROPER_FRACTION_RULE_INDEX];
}
else if (nonNumericalRules[IMPROPER_FRACTION_RULE_INDEX] != null) {
// otherwise, return the improper fraction rule
return nonNumericalRules[IMPROPER_FRACTION_RULE_INDEX];
}
}
// if there's a default rule, use it to format the number
if (nonNumericalRules[DEFAULT_RULE_INDEX] != null) {
return nonNumericalRules[DEFAULT_RULE_INDEX];
}
else {
// and if we haven't yet returned a rule, use findNormalRule()
// to find the applicable rule
return findNormalRule(Math.round(number));
}
}
/**
* If the value passed to findRule() is a positive integer, findRule()
* uses this function to select the appropriate rule. The result will
* generally be the rule with the highest base value less than or equal
* to the number. There is one exception to this: If that rule has
* two substitutions and a base value that is not an even multiple of
* its divisor, and the number itself IS an even multiple of the rule's
* divisor, then the result will be the rule that preceded the original
* result in the rule list. (This behavior is known as the "rollback
* rule", and is used to handle optional text: a rule with optional
* text is represented internally as two rules, and the rollback rule
* selects appropriate between them. This avoids things like "two
* hundred zero".)
* @param number The number being formatted
* @return The rule to use to format this number
*/
private NFRule findNormalRule(long number) {
// if this is a fraction rule set, use findFractionRuleSetRule()
// to find the rule (we should only go into this clause if the
// value is 0)
if (isFractionRuleSet) {
return findFractionRuleSetRule(number);
}
// if the number is negative, return the negative-number rule
// (if there isn't one, pretend the number is positive)
if (number < 0) {
if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
return nonNumericalRules[NEGATIVE_RULE_INDEX];
} else {
number = -number;
}
}
// we have to repeat the preceding two checks, even though we
// do them in findRule(), because the version of format() that
// takes a long bypasses findRule() and goes straight to this
// function. This function does skip the fraction rules since
// we know the value is an integer (it also skips the default
// rule, since it's considered a fraction rule. Skipping the
// default rule in this function is also how we avoid infinite
// recursion)
// binary-search the rule list for the applicable rule
// (a rule is used for all values from its base value to
// the next rule's base value)
int lo = 0;
int hi = rules.length;
if (hi > 0) {
while (lo < hi) {
int mid = (lo + hi) >>> 1;
long ruleBaseValue = rules[mid].getBaseValue();
if (ruleBaseValue == number) {
return rules[mid];
}
else if (ruleBaseValue > number) {
hi = mid;
}
else {
lo = mid + 1;
}
}
if (hi == 0) { // bad rule set
throw new IllegalStateException("The rule set " + name + " cannot format the value " + number);
}
NFRule result = rules[hi - 1];
// use shouldRollBack() to see whether we need to invoke the
// rollback rule (see shouldRollBack()'s documentation for
// an explanation of the rollback rule). If we do, roll back
// one rule and return that one instead of the one we'd normally
// return
if (result.shouldRollBack(number)) {
if (hi == 1) { // bad rule set
throw new IllegalStateException("The rule set " + name + " cannot roll back from the rule '" +
result + "'");
}
result = rules[hi - 2];
}
return result;
}
// else use the default rule
return nonNumericalRules[DEFAULT_RULE_INDEX];
}
/**
* If this rule is a fraction rule set, this function is used by
* findRule() to select the most appropriate rule for formatting
* the number. Basically, the base value of each rule in the rule
* set is treated as the denominator of a fraction. Whichever
* denominator can produce the fraction closest in value to the
* number passed in is the result. If there's a tie, the earlier
* one in the list wins. (If there are two rules in a row with the
* same base value, the first one is used when the numerator of the
* fraction would be 1, and the second rule is used the rest of the
* time.
* @param number The number being formatted (which will always be
* a number between 0 and 1)
* @return The rule to use to format this number
*/
private NFRule findFractionRuleSetRule(double number) {
// the obvious way to do this (multiply the value being formatted
// by each rule's base value until you get an integral result)
// doesn't work because of rounding error. This method is more
// accurate
// find the least common multiple of the rules' base values
// and multiply this by the number being formatted. This is
// all the precision we need, and we can do all of the rest
// of the math using integer arithmetic
long leastCommonMultiple = rules[0].getBaseValue();
for (int i = 1; i < rules.length; i++) {
leastCommonMultiple = lcm(leastCommonMultiple, rules[i].getBaseValue());
}
long numerator = Math.round(number * leastCommonMultiple);
// for each rule, do the following...
long tempDifference;
long difference = Long.MAX_VALUE;
int winner = 0;
for (int i = 0; i < rules.length; i++) {
// "numerator" is the numerator of the fraction is the
// denominator is the LCD. The numerator if the the rule's
// base value is the denominator is "numerator" times the
// base value divided by the LCD. Here we check to see if
// that's an integer, and if not, how close it is to being
// an integer.
tempDifference = numerator * rules[i].getBaseValue() % leastCommonMultiple;
// normalize the result of the above calculation: we want
// the numerator's distance from the CLOSEST multiple
// of the LCD
if (leastCommonMultiple - tempDifference < tempDifference) {
tempDifference = leastCommonMultiple - tempDifference;
}
// if this is as close as we've come, keep track of how close
// that is, and the line number of the rule that did it. If
// we've scored a direct hit, we don't have to look at any more
// rules
if (tempDifference < difference) {
difference = tempDifference;
winner = i;
if (difference == 0) {
break;
}
}
}
// if we have two successive rules that both have the winning base
// value, then the first one (the one we found above) is used if
// the numerator of the fraction is 1 and the second one is used if
// the numerator of the fraction is anything else (this lets us
// do things like "one third"/"two thirds" without having to define
// a whole bunch of extra rule sets)
if (winner + 1 < rules.length
&& rules[winner + 1].getBaseValue() == rules[winner].getBaseValue()) {
if (Math.round(number * rules[winner].getBaseValue()) < 1
|| Math.round(number * rules[winner].getBaseValue()) >= 2) {
++winner;
}
}
// finally, return the winning rule
return rules[winner];
}
/**
* Calculates the least common multiple of x and y.
*/
private static long lcm(long x, long y) {
// binary gcd algorithm from Knuth, "The Art of Computer Programming,"
// vol. 2, 1st ed., pp. 298-299
long x1 = x;
long y1 = y;
int p2 = 0;
while ((x1 & 1) == 0 && (y1 & 1) == 0) {
++p2;
x1 >>= 1;
y1 >>= 1;
}
long t;
if ((x1 & 1) == 1) {
t = -y1;
} else {
t = x1;
}
while (t != 0) {
while ((t & 1) == 0) {
t >>= 1;
}
if (t > 0) {
x1 = t;
} else {
y1 = -t;
}
t = x1 - y1;
}
long gcd = x1 << p2;
// x * y == gcd(x, y) * lcm(x, y)
return x / gcd * y;
}
//-----------------------------------------------------------------------
// parsing
//-----------------------------------------------------------------------
/**
* Parses a string. Matches the string to be parsed against each
* of its rules (with a base value less than upperBound) and returns
* the value produced by the rule that matched the most characters
* in the source string.
* @param text The string to parse
* @param parsePosition The initial position is ignored and assumed
* to be 0. On exit, this object has been updated to point to the
* first character position this rule set didn't consume.
* @param upperBound Limits the rules that can be allowed to match.
* Only rules whose base values are strictly less than upperBound
* are considered.
* @return The numerical result of parsing this string. This will
* be the matching rule's base value, composed appropriately with
* the results of matching any of its substitutions. The object
* will be an instance of Long if it's an integral value; otherwise,
* it will be an instance of Double. This function always returns
* a valid object: If nothing matched the input string at all,
* this function returns new Long(0), and the parse position is
* left unchanged.
*/
public Number parse(String text, ParsePosition parsePosition, double upperBound, int nonNumericalExecutedRuleMask) {
// try matching each rule in the rule set against the text being
// parsed. Whichever one matches the most characters is the one
// that determines the value we return.
ParsePosition highWaterMark = new ParsePosition(0);
Number result = NFRule.ZERO;
Number tempResult;
// dump out if there's no text to parse
if (text.length() == 0) {
return result;
}
// Try each of the negative rules, fraction rules, infinity rules and NaN rules
for (int nonNumericalRuleIdx = 0; nonNumericalRuleIdx < nonNumericalRules.length; nonNumericalRuleIdx++) {
NFRule nonNumericalRule = nonNumericalRules[nonNumericalRuleIdx];
if (nonNumericalRule != null && ((nonNumericalExecutedRuleMask >> nonNumericalRuleIdx) & 1) == 0) {
// Mark this rule as being executed so that we don't try to execute it again.
nonNumericalExecutedRuleMask |= 1 << nonNumericalRuleIdx;
tempResult = nonNumericalRule.doParse(text, parsePosition, false, upperBound, nonNumericalExecutedRuleMask);
if (parsePosition.getIndex() > highWaterMark.getIndex()) {
result = tempResult;
highWaterMark.setIndex(parsePosition.getIndex());
}
// commented out because the error-index API on ParsePosition isn't there in 1.1.x
// if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
// highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
// }
parsePosition.setIndex(0);
}
}
// finally, go through the regular rules one at a time. We start
// at the end of the list because we want to try matching the most
// significant rule first (this helps ensure that we parse
// "five thousand three hundred six" as
// "(five thousand) (three hundred) (six)" rather than
// "((five thousand three) hundred) (six)"). Skip rules whose
// base values are higher than the upper bound (again, this helps
// limit ambiguity by making sure the rules that match a rule's
// are less significant than the rule containing the substitutions)/
for (int i = rules.length - 1; i >= 0 && highWaterMark.getIndex() < text.length(); i--) {
if (!isFractionRuleSet && rules[i].getBaseValue() >= upperBound) {
continue;
}
tempResult = rules[i].doParse(text, parsePosition, isFractionRuleSet, upperBound, nonNumericalExecutedRuleMask);
if (parsePosition.getIndex() > highWaterMark.getIndex()) {
result = tempResult;
highWaterMark.setIndex(parsePosition.getIndex());
}
// commented out because the error-index API on ParsePosition isn't there in 1.1.x
// if (parsePosition.getErrorIndex() > highWaterMark.getErrorIndex()) {
// highWaterMark.setErrorIndex(parsePosition.getErrorIndex());
// }
parsePosition.setIndex(0);
}
// finally, update the parse position we were passed to point to the
// first character we didn't use, and return the result that
// corresponds to that string of characters
parsePosition.setIndex(highWaterMark.getIndex());
// commented out because the error-index API on ParsePosition isn't there in 1.1.x
// if (parsePosition.getIndex() == 0) {
// parsePosition.setErrorIndex(highWaterMark.getErrorIndex());
// }
return result;
}
public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) {
for (NFRule rule : rules) {
rule.setDecimalFormatSymbols(newSymbols);
}
// Switch the fraction rules to mirror the DecimalFormatSymbols.
if (fractionRules != null) {
for (int nonNumericalIdx = IMPROPER_FRACTION_RULE_INDEX; nonNumericalIdx <= DEFAULT_RULE_INDEX; nonNumericalIdx++) {
if (nonNumericalRules[nonNumericalIdx] != null) {
for (NFRule rule : fractionRules) {
if (nonNumericalRules[nonNumericalIdx].getBaseValue() == rule.getBaseValue()) {
setBestFractionRule(nonNumericalIdx, rule, false);
}
}
}
}
}
for (NFRule rule : nonNumericalRules) {
if (rule != null) {
rule.setDecimalFormatSymbols(newSymbols);
}
}
}
}