com.ibm.icu.text.NFRuleSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 1996-2015, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.text.ParsePosition;
import 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);
}
}
}
}