ICU-11760 Add rule syntax for NaN, infinity and alternate decimal points.

Some performance enhancements were added for good measure too.  Creating new RuleBasedNumberFormat objects can take a long time due to all the rule parsing.
 Also I fixed a potential infinite recursion problem when RuleBasedNumberFormat used NumberFormat.createInstance, which could occasionally depend on creating RuleBasedNumberFormat for itself, which was bad.

X-SVN-Rev: 37778
This commit is contained in:
George Rhoten 2015-08-17 08:38:34 +00:00
parent 03eab980da
commit 8653b95982
6 changed files with 544 additions and 420 deletions

View File

@ -1,6 +1,6 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 1996-2014, International Business Machines Corporation and * * Copyright (C) 1996-2015, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
*/ */
@ -697,7 +697,11 @@ public class DecimalFormat extends NumberFormat {
private void createFromPatternAndSymbols(String pattern, DecimalFormatSymbols inputSymbols) { private void createFromPatternAndSymbols(String pattern, DecimalFormatSymbols inputSymbols) {
// Always applyPattern after the symbols are set // Always applyPattern after the symbols are set
symbols = (DecimalFormatSymbols) inputSymbols.clone(); symbols = (DecimalFormatSymbols) inputSymbols.clone();
setCurrencyForSymbols(); if (pattern.indexOf(CURRENCY_SIGN) >= 0) {
// Only spend time with currency symbols when we're going to display it.
// Also set some defaults before the apply pattern.
setCurrencyForSymbols();
}
applyPatternWithoutExpandAffix(pattern, false); applyPatternWithoutExpandAffix(pattern, false);
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) { if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale()); currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
@ -2332,7 +2336,7 @@ public class DecimalFormat extends NumberFormat {
* @param negSuffix negative suffix pattern * @param negSuffix negative suffix pattern
* @param posPrefix positive prefix pattern * @param posPrefix positive prefix pattern
* @param negSuffix negative suffix pattern * @param negSuffix negative suffix pattern
* @param complexCurrencyParsing whether it is complex currency parsing or not. * @param parseComplexCurrency whether it is complex currency parsing or not.
* @param type type of currency to parse against, LONG_NAME only or not. * @param type type of currency to parse against, LONG_NAME only or not.
*/ */
private final boolean subparse( private final boolean subparse(
@ -2779,7 +2783,7 @@ public class DecimalFormat extends NumberFormat {
* @param isNegative * @param isNegative
* @param isPrefix * @param isPrefix
* @param affixPat affix pattern used for currency affix comparison * @param affixPat affix pattern used for currency affix comparison
* @param copmplexCurrencyParsing whether it is currency parsing or not * @param complexCurrencyParsing whether it is currency parsing or not
* @param type compare against currency type, LONG_NAME only or not. * @param type compare against currency type, LONG_NAME only or not.
* @param currency return value for parsed currency, for generic currency parsing * @param currency return value for parsed currency, for generic currency parsing
* mode, or null for normal parsing. In generic currency parsing mode, any currency * mode, or null for normal parsing. In generic currency parsing mode, any currency

View File

@ -11,6 +11,7 @@ import java.text.ParsePosition;
import java.util.List; import java.util.List;
import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.Utility;
/** /**
* A class representing a single rule in a RuleBasedNumberFormat. A rule * A class representing a single rule in a RuleBasedNumberFormat. A rule
@ -25,22 +26,34 @@ final class NFRule {
/** /**
* Special base value used to identify a negative-number rule * Special base value used to identify a negative-number rule
*/ */
public static final int NEGATIVE_NUMBER_RULE = -1; static final int NEGATIVE_NUMBER_RULE = -1;
/** /**
* Special base value used to identify an improper fraction (x.x) rule * Special base value used to identify an improper fraction (x.x) rule
*/ */
public static final int IMPROPER_FRACTION_RULE = -2; static final int IMPROPER_FRACTION_RULE = -2;
/** /**
* Special base value used to identify a proper fraction (0.x) rule * Special base value used to identify a proper fraction (0.x) rule
*/ */
public static final int PROPER_FRACTION_RULE = -3; static final int PROPER_FRACTION_RULE = -3;
/** /**
* Special base value used to identify a master rule * Special base value used to identify a master rule
*/ */
public static final int MASTER_RULE = -4; static final int MASTER_RULE = -4;
/**
* Special base value used to identify an infinity rule
*/
static final int INFINITY_RULE = -5;
/**
* Special base value used to identify a not a number rule
*/
static final int NAN_RULE = -6;
static final Long ZERO = (long) 0;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// data members // data members
@ -63,6 +76,11 @@ final class NFRule {
*/ */
private short exponent = 0; private short exponent = 0;
/**
* If this is a fraction rule, this is the decimal point from DecimalFormatSymbols to match.
*/
private char decimalPoint = 0;
/** /**
* The rule's rule text. When formatting a number, the rule's text * The rule's rule text. When formatting a number, the rule's text
* is inserted into the result string, and then the text from any * is inserted into the result string, and then the text from any
@ -117,21 +135,23 @@ final class NFRule {
// new it up and initialize its basevalue and divisor // new it up and initialize its basevalue and divisor
// (this also strips the rule descriptor, if any, off the // (this also strips the rule descriptor, if any, off the
// description string) // description string)
NFRule rule1 = new NFRule(ownersOwner); NFRule rule1 = new NFRule(ownersOwner, description);
description = rule1.parseRuleDescriptor(description); description = rule1.ruleText;
// check the description to see whether there's text enclosed // check the description to see whether there's text enclosed
// in brackets // in brackets
int brack1 = description.indexOf("["); int brack1 = description.indexOf('[');
int brack2 = description.indexOf("]"); int brack2 = brack1 < 0 ? -1 : description.indexOf(']');
// if the description doesn't contain a matched pair of brackets, // if the description doesn't contain a matched pair of brackets,
// or if it's of a type that doesn't recognize bracketed text, // or if it's of a type that doesn't recognize bracketed text,
// then leave the description alone, initialize the rule's // then leave the description alone, initialize the rule's
// rule text and substitutions, and return that rule // rule text and substitutions, and return that rule
if (brack1 == -1 || brack2 == -1 || brack1 > brack2 if (brack2 < 0 || brack1 > brack2
|| rule1.getBaseValue() == PROPER_FRACTION_RULE || rule1.baseValue == PROPER_FRACTION_RULE
|| rule1.getBaseValue() == NEGATIVE_NUMBER_RULE) || rule1.baseValue == NEGATIVE_NUMBER_RULE
|| rule1.baseValue == INFINITY_RULE
|| rule1.baseValue == NAN_RULE)
{ {
rule1.extractSubstitutions(owner, description, predecessor); rule1.extractSubstitutions(owner, description, predecessor);
} }
@ -155,7 +175,7 @@ final class NFRule {
// set, they both have the same base value; otherwise, // set, they both have the same base value; otherwise,
// increment the original rule's base value ("rule1" actually // increment the original rule's base value ("rule1" actually
// goes SECOND in the rule set's rule list) // goes SECOND in the rule set's rule list)
rule2 = new NFRule(ownersOwner); rule2 = new NFRule(ownersOwner, null);
if (rule1.baseValue >= 0) { if (rule1.baseValue >= 0) {
rule2.baseValue = rule1.baseValue; rule2.baseValue = rule1.baseValue;
if (!owner.isFractionSet()) { if (!owner.isFractionSet()) {
@ -207,18 +227,29 @@ final class NFRule {
// material in the brackets and rule1 INCLUDES the material // material in the brackets and rule1 INCLUDES the material
// in the brackets) // in the brackets)
if (rule2 != null) { if (rule2 != null) {
returnList.add(rule2); if (rule2.baseValue >= 0) {
returnList.add(rule2);
}
else {
owner.setNonNumericalRule(rule2);
}
} }
} }
returnList.add(rule1); if (rule1.baseValue >= 0) {
returnList.add(rule1);
}
else {
owner.setNonNumericalRule(rule1);
}
} }
/** /**
* Nominal constructor for NFRule. Most of the work of constructing * Nominal constructor for NFRule. Most of the work of constructing
* an NFRule is actually performed by makeRules(). * an NFRule is actually performed by makeRules().
*/ */
public NFRule(RuleBasedNumberFormat formatter) { public NFRule(RuleBasedNumberFormat formatter, String ruleText) {
this.formatter = formatter; this.formatter = formatter;
this.ruleText = ruleText == null ? null : parseRuleDescriptor(ruleText);
} }
/** /**
@ -240,10 +271,7 @@ final class NFRule {
// separated by a colon. The rule descriptor is optional. If // separated by a colon. The rule descriptor is optional. If
// it's omitted, just set the base value to 0. // it's omitted, just set the base value to 0.
int p = description.indexOf(":"); int p = description.indexOf(":");
if (p == -1) { if (p != -1) {
setBaseValue(0);
}
else {
// copy the descriptor out into its own string and strip it, // copy the descriptor out into its own string and strip it,
// along with any trailing whitespace, out of the original // along with any trailing whitespace, out of the original
// description // description
@ -257,14 +285,13 @@ final class NFRule {
// check first to see if the rule descriptor matches the token // check first to see if the rule descriptor matches the token
// for one of the special rules. If it does, set the base // for one of the special rules. If it does, set the base
// value to the correct identifier value // value to the correct identifier value
if (descriptor.equals("0.x")) { int descriptorLength = descriptor.length();
setBaseValue(PROPER_FRACTION_RULE); char firstChar = descriptor.charAt(0);
} char lastChar = descriptor.charAt(descriptorLength - 1);
else if (descriptor.charAt(0) >= '0' && descriptor.charAt(0) <= '9') { if (firstChar >= '0' && firstChar <= '9' && lastChar != 'x') {
// if the rule descriptor begins with a digit, it's a descriptor // if the rule descriptor begins with a digit, it's a descriptor
// for a normal rule // for a normal rule
long tempValue = 0; long tempValue = 0;
int descriptorLength = descriptor.length();
char c = 0; char c = 0;
p = 0; p = 0;
@ -339,13 +366,28 @@ final class NFRule {
else if (descriptor.equals("-x")) { else if (descriptor.equals("-x")) {
setBaseValue(NEGATIVE_NUMBER_RULE); setBaseValue(NEGATIVE_NUMBER_RULE);
} }
else if (descriptor.equals("x.x")) { else if (descriptorLength == 3) {
setBaseValue(IMPROPER_FRACTION_RULE); if (firstChar == '0' && lastChar == 'x') {
} setBaseValue(PROPER_FRACTION_RULE);
else if (descriptor.equals("x.0")) { decimalPoint = descriptor.charAt(1);
setBaseValue(MASTER_RULE); }
else if (firstChar == 'x' && lastChar == 'x') {
setBaseValue(IMPROPER_FRACTION_RULE);
decimalPoint = descriptor.charAt(1);
}
else if (firstChar == 'x' && lastChar == '0') {
setBaseValue(MASTER_RULE);
decimalPoint = descriptor.charAt(1);
}
else if (descriptor.equals("NaN")) {
setBaseValue(NAN_RULE);
}
else if (descriptor.equals("Inf")) {
setBaseValue(INFINITY_RULE);
}
} }
} }
// else use the default base value for now.
// finally, if the rule body begins with an apostrophe, strip it off // finally, if the rule body begins with an apostrophe, strip it off
// (this is generally used to put whitespace at the beginning of // (this is generally used to put whitespace at the beginning of
@ -371,11 +413,10 @@ final class NFRule {
String ruleText, String ruleText,
NFRule predecessor) { NFRule predecessor) {
this.ruleText = ruleText; this.ruleText = ruleText;
this.rulePatternFormat = null;
sub1 = extractSubstitution(owner, predecessor); sub1 = extractSubstitution(owner, predecessor);
if (sub1.isNullSubstitution()) { if (sub1 == null) {
// Small optimization. There is no need to create a redundant NullSubstitution. // Small optimization. There is no need to create a redundant NullSubstitution.
sub2 = sub1; sub2 = null;
} }
else { else {
sub2 = extractSubstitution(owner, predecessor); sub2 = extractSubstitution(owner, predecessor);
@ -404,12 +445,6 @@ final class NFRule {
} }
} }
private static final String[] RULE_PREFIXES = new String[] {
"<<", "<%", "<#", "<0",
">>", ">%", ">#", ">0",
"=%", "=#", "=0"
};
/** /**
* Searches the rule's rule text for the first substitution token, * Searches the rule's rule text for the first substitution token,
* creates a substitution based on it, and removes the token from * creates a substitution based on it, and removes the token from
@ -429,18 +464,17 @@ final class NFRule {
// search the rule's rule text for the first two characters of // search the rule's rule text for the first two characters of
// a substitution token // a substitution token
subStart = indexOfAny(ruleText, RULE_PREFIXES); subStart = indexOfAnyRulePrefix(ruleText);
// if we didn't find one, create a null substitution positioned // if we didn't find one, create a null substitution positioned
// at the end of the rule text // at the end of the rule text
if (subStart == -1) { if (subStart == -1) {
return NFSubstitution.makeSubstitution(ruleText.length(), this, predecessor, return null;
owner, this.formatter, "");
} }
// special-case the ">>>" token, since searching for the > at the // special-case the ">>>" token, since searching for the > at the
// end will actually find the > in the middle // end will actually find the > in the middle
if (ruleText.substring(subStart).startsWith(">>>")) { if (ruleText.startsWith(">>>", subStart)) {
subEnd = subStart + 2; subEnd = subStart + 2;
} }
else { else {
@ -462,8 +496,7 @@ final class NFRule {
// unmatched token character), create a null substitution positioned // unmatched token character), create a null substitution positioned
// at the end of the rule // at the end of the rule
if (subEnd == -1) { if (subEnd == -1) {
return NFSubstitution.makeSubstitution(ruleText.length(), this, predecessor, return null;
owner, this.formatter, "");
} }
// if we get here, we have a real substitution token (or at least // if we get here, we have a real substitution token (or at least
@ -487,6 +520,7 @@ final class NFRule {
final void setBaseValue(long newBaseValue) { final void setBaseValue(long newBaseValue) {
// set the base value // set the base value
baseValue = newBaseValue; baseValue = newBaseValue;
radix = 10;
// if this isn't a special rule, recalculate the radix and exponent // if this isn't a special rule, recalculate the radix and exponent
// (the radix always defaults to 10; if it's supposed to be something // (the radix always defaults to 10; if it's supposed to be something
@ -494,7 +528,6 @@ final class NFRule {
// recalculated again-- the only function that does this is // recalculated again-- the only function that does this is
// NFRule.parseRuleDescriptor() ) // NFRule.parseRuleDescriptor() )
if (baseValue >= 1) { if (baseValue >= 1) {
radix = 10;
exponent = expectedExponent(); exponent = expectedExponent();
// this function gets called on a fully-constructed rule whose // this function gets called on a fully-constructed rule whose
@ -511,7 +544,6 @@ final class NFRule {
else { else {
// if this is a special rule, its radix and exponent are basically // if this is a special rule, its radix and exponent are basically
// ignored. Set them to "safe" default values // ignored. Set them to "safe" default values
radix = 10;
exponent = 0; exponent = 0;
} }
} }
@ -540,20 +572,24 @@ final class NFRule {
} }
} }
private static final String[] RULE_PREFIXES = new String[] {
"<<", "<%", "<#", "<0",
">>", ">%", ">#", ">0",
"=%", "=#", "=0"
};
/** /**
* Searches the rule's rule text for any of the specified strings. * Searches the rule's rule text for any of the specified strings.
* @param strings An array of strings to search the rule's rule
* text for
* @return The index of the first match in the rule's rule text * @return The index of the first match in the rule's rule text
* (i.e., the first substring in the rule's rule text that matches * (i.e., the first substring in the rule's rule text that matches
* _any_ of the strings in "strings"). If none of the strings in * _any_ of the strings in "strings"). If none of the strings in
* "strings" is found in the rule's rule text, returns -1. * "strings" is found in the rule's rule text, returns -1.
*/ */
private static int indexOfAny(String ruleText, String[] strings) { private static int indexOfAnyRulePrefix(String ruleText) {
int result = -1; int result = -1;
if (ruleText.length() > 0) { if (ruleText.length() > 0) {
int pos; int pos;
for (String string : strings) { for (String string : RULE_PREFIXES) {
pos = ruleText.indexOf(string); pos = ruleText.indexOf(string);
if (pos != -1 && (result == -1 || pos < result)) { if (pos != -1 && (result == -1 || pos < result)) {
result = pos; result = pos;
@ -580,8 +616,8 @@ final class NFRule {
&& radix == that2.radix && radix == that2.radix
&& exponent == that2.exponent && exponent == that2.exponent
&& ruleText.equals(that2.ruleText) && ruleText.equals(that2.ruleText)
&& sub1.equals(that2.sub1) && Utility.objectEquals(sub1, that2.sub1)
&& sub2.equals(that2.sub2); && Utility.objectEquals(sub2, that2.sub2);
} }
return false; return false;
} }
@ -605,13 +641,19 @@ final class NFRule {
result.append("-x: "); result.append("-x: ");
} }
else if (baseValue == IMPROPER_FRACTION_RULE) { else if (baseValue == IMPROPER_FRACTION_RULE) {
result.append("x.x: "); result.append('x').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: ");
} }
else if (baseValue == PROPER_FRACTION_RULE) { else if (baseValue == PROPER_FRACTION_RULE) {
result.append("0.x: "); result.append('0').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: ");
} }
else if (baseValue == MASTER_RULE) { else if (baseValue == MASTER_RULE) {
result.append("x.0: "); result.append('x').append(decimalPoint == 0 ? '.' : decimalPoint).append("0: ");
}
else if (baseValue == INFINITY_RULE) {
result.append("Inf: ");
}
else if (baseValue == NAN_RULE) {
result.append("NaN: ");
} }
else { else {
// for a normal rule, write out its base value, and if the radix is // for a normal rule, write out its base value, and if the radix is
@ -634,14 +676,18 @@ final class NFRule {
// (whitespace after the rule descriptor is ignored; the // (whitespace after the rule descriptor is ignored; the
// apostrophe is used to make the whitespace significant) // apostrophe is used to make the whitespace significant)
if (ruleText.startsWith(" ") && (sub1 == null || sub1.getPos() != 0)) { if (ruleText.startsWith(" ") && (sub1 == null || sub1.getPos() != 0)) {
result.append("\'"); result.append('\'');
} }
// now, write the rule's rule text, inserting appropriate // now, write the rule's rule text, inserting appropriate
// substitution tokens in the appropriate places // substitution tokens in the appropriate places
StringBuilder ruleTextCopy = new StringBuilder(ruleText); StringBuilder ruleTextCopy = new StringBuilder(ruleText);
ruleTextCopy.insert(sub2.getPos(), sub2.toString()); if (sub2 != null) {
ruleTextCopy.insert(sub1.getPos(), sub1.toString()); ruleTextCopy.insert(sub2.getPos(), sub2.toString());
}
if (sub1 != null) {
ruleTextCopy.insert(sub1.getPos(), sub1.toString());
}
result.append(ruleTextCopy.toString()); result.append(ruleTextCopy.toString());
// and finally, top the whole thing off with a semicolon and // and finally, top the whole thing off with a semicolon and
@ -654,6 +700,14 @@ final class NFRule {
// simple accessors // simple accessors
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/**
* Returns the rule's base value
* @return The rule's base value
*/
public final char getDecimalPoint() {
return decimalPoint;
}
/** /**
* Returns the rule's base value * Returns the rule's base value
* @return The rule's base value * @return The rule's base value
@ -708,10 +762,10 @@ final class NFRule {
} }
lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength); lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength);
} }
if (!sub2.isNullSubstitution()) { if (sub2 != null) {
sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount);
} }
if (!sub1.isNullSubstitution()) { if (sub1 != null) {
sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount);
} }
} }
@ -750,10 +804,10 @@ final class NFRule {
} }
lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength); lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength);
} }
if (!sub2.isNullSubstitution()) { if (sub2 != null) {
sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); sub2.doSubstitution(number, toInsertInto, pos - (sub2.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount);
} }
if (!sub1.isNullSubstitution()) { if (sub1 != null) {
sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount); sub1.doSubstitution(number, toInsertInto, pos - (sub1.getPos() > pluralRuleStart ? lengthOffset : 0), recursionCount);
} }
} }
@ -783,7 +837,7 @@ final class NFRule {
// a modulus substitution, its base value isn't an even multiple // a modulus substitution, its base value isn't an even multiple
// of 100, and the value we're trying to format _is_ an even // of 100, and the value we're trying to format _is_ an even
// multiple of 100. This is called the "rollback rule." // multiple of 100. This is called the "rollback rule."
return ((sub1.isModulusSubstitution()) || (sub2.isModulusSubstitution())) return ((sub1 != null && sub1.isModulusSubstitution()) || (sub2 != null && sub2.isModulusSubstitution()))
&& (number % Math.pow(radix, exponent)) == 0 && (baseValue % Math.pow(radix, exponent)) != 0; && (number % Math.pow(radix, exponent)) == 0 && (baseValue % Math.pow(radix, exponent)) != 0;
} }
@ -820,13 +874,25 @@ final class NFRule {
// matches the text at the beginning of the string being // matches the text at the beginning of the string being
// parsed. If it does, strip that off the front of workText; // parsed. If it does, strip that off the front of workText;
// otherwise, dump out with a mismatch // otherwise, dump out with a mismatch
String workText = stripPrefix(text, ruleText.substring(0, sub1.getPos()), pp); int sub1Pos = sub1 != null ? sub1.getPos() : ruleText.length();
int sub2Pos = sub2 != null ? sub2.getPos() : ruleText.length();
String workText = stripPrefix(text, ruleText.substring(0, sub1Pos), pp);
int prefixLength = text.length() - workText.length(); int prefixLength = text.length() - workText.length();
if (pp.getIndex() == 0 && sub1.getPos() != 0) { if (pp.getIndex() == 0 && sub1Pos != 0) {
// commented out because ParsePosition doesn't have error index in 1.1.x // commented out because ParsePosition doesn't have error index in 1.1.x
// parsePosition.setErrorIndex(pp.getErrorIndex()); // parsePosition.setErrorIndex(pp.getErrorIndex());
return Long.valueOf(0); return ZERO;
}
if (baseValue == INFINITY_RULE) {
// If you match this, don't try to perform any calculations on it.
parsePosition.setIndex(pp.getIndex());
return Double.POSITIVE_INFINITY;
}
if (baseValue == NAN_RULE) {
// If you match this, don't try to perform any calculations on it.
parsePosition.setIndex(pp.getIndex());
return Double.NaN;
} }
// this is the fun part. The basic guts of the rule-matching // this is the fun part. The basic guts of the rule-matching
@ -870,14 +936,14 @@ final class NFRule {
// the substitution, giving us a new partial parse result // the substitution, giving us a new partial parse result
pp.setIndex(0); pp.setIndex(0);
double partialResult = matchToDelimiter(workText, start, tempBaseValue, double partialResult = matchToDelimiter(workText, start, tempBaseValue,
ruleText.substring(sub1.getPos(), sub2.getPos()), rulePatternFormat, ruleText.substring(sub1Pos, sub2Pos), rulePatternFormat,
pp, sub1, upperBound).doubleValue(); pp, sub1, upperBound).doubleValue();
// if we got a successful match (or were trying to match a // if we got a successful match (or were trying to match a
// null substitution), pp is now pointing at the first unmatched // null substitution), pp is now pointing at the first unmatched
// character. Take note of that, and try matchToDelimiter() // character. Take note of that, and try matchToDelimiter()
// on the input text again // on the input text again
if (pp.getIndex() != 0 || sub1.isNullSubstitution()) { if (pp.getIndex() != 0 || sub1 == null) {
start = pp.getIndex(); start = pp.getIndex();
String workText2 = workText.substring(pp.getIndex()); String workText2 = workText.substring(pp.getIndex());
@ -888,13 +954,13 @@ final class NFRule {
// substitution if there's a successful match, giving us // substitution if there's a successful match, giving us
// a real result // a real result
partialResult = matchToDelimiter(workText2, 0, partialResult, partialResult = matchToDelimiter(workText2, 0, partialResult,
ruleText.substring(sub2.getPos()), rulePatternFormat, pp2, sub2, ruleText.substring(sub2Pos), rulePatternFormat, pp2, sub2,
upperBound).doubleValue(); upperBound).doubleValue();
// if we got a successful match on this second // if we got a successful match on this second
// matchToDelimiter() call, update the high-water mark // matchToDelimiter() call, update the high-water mark
// and result (if necessary) // and result (if necessary)
if (pp2.getIndex() != 0 || sub2.isNullSubstitution()) { if (pp2.getIndex() != 0 || sub2 == null) {
if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) { if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) {
highWaterMark = prefixLength + pp.getIndex() + pp2.getIndex(); highWaterMark = prefixLength + pp.getIndex() + pp2.getIndex();
result = partialResult; result = partialResult;
@ -918,7 +984,8 @@ final class NFRule {
// keep trying to match things until the outer matchToDelimiter() // keep trying to match things until the outer matchToDelimiter()
// call fails to make a match (each time, it picks up where it // call fails to make a match (each time, it picks up where it
// left off the previous time) // left off the previous time)
} while (sub1.getPos() != sub2.getPos() && pp.getIndex() > 0 && pp.getIndex() }
while (sub1Pos != sub2Pos && pp.getIndex() > 0 && pp.getIndex()
< workText.length() && pp.getIndex() != start); < workText.length() && pp.getIndex() != start);
// update the caller's ParsePosition with our high-water mark // update the caller's ParsePosition with our high-water mark
@ -937,7 +1004,7 @@ final class NFRule {
// we have to account for it here. By definition, if the matching // we have to account for it here. By definition, if the matching
// rule in a fraction rule set has no substitutions, its numerator // rule in a fraction rule set has no substitutions, its numerator
// is 1, and so the result is the reciprocal of its base value. // is 1, and so the result is the reciprocal of its base value.
if (isFractionRule && highWaterMark > 0 && sub1.isNullSubstitution()) { if (isFractionRule && highWaterMark > 0 && sub1 == null) {
result = 1 / result; result = 1 / result;
} }
@ -1071,21 +1138,23 @@ final class NFRule {
// if we make it here, this was an unsuccessful match, and we // if we make it here, this was an unsuccessful match, and we
// leave pp unchanged and return 0 // leave pp unchanged and return 0
pp.setIndex(0); pp.setIndex(0);
return Long.valueOf(0); return ZERO;
// if "delimiter" is empty, or consists only of ignorable characters // if "delimiter" is empty, or consists only of ignorable characters
// (i.e., is semantically empty), thwe we obviously can't search // (i.e., is semantically empty), thwe we obviously can't search
// for "delimiter". Instead, just use "sub" to parse as much of // for "delimiter". Instead, just use "sub" to parse as much of
// "text" as possible. // "text" as possible.
} else { }
else if (sub == null) {
return baseVal;
}
else {
ParsePosition tempPP = new ParsePosition(0); ParsePosition tempPP = new ParsePosition(0);
Number result = Long.valueOf(0); Number result = ZERO;
Number tempResult;
// try to match the whole string against the substitution // try to match the whole string against the substitution
tempResult = sub.doParse(text, tempPP, baseVal, upperBound, Number tempResult = sub.doParse(text, tempPP, baseVal, upperBound,
formatter.lenientParseEnabled()); formatter.lenientParseEnabled());
if (tempPP.getIndex() != 0 || sub.isNullSubstitution()) { if (tempPP.getIndex() != 0) {
// if there's a successful match (or it's a null // if there's a successful match (or it's a null
// substitution), update pp to point to the first // substitution), update pp to point to the first
// character we didn't match, and pass the result from // character we didn't match, and pass the result from

View File

@ -8,6 +8,7 @@ package com.ibm.icu.text;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.PatternProps;
@ -37,16 +38,33 @@ final class NFRuleSet {
private NFRule[] rules; private NFRule[] rules;
/** /**
* The rule set's negative-number rule * The rule set's non-numerical rules like negative, fractions, infinity and NaN
*/ */
private NFRule negativeNumberRule = null; final NFRule[] nonNumericalRules = new NFRule[6];
/** /**
* The rule set's fraction rules: element 0 is the proper fraction * These are a pile of fraction rules in declared order. They may have alternate
* (0.x) rule, element 1 is the improper fraction (x.x) rule, and * ways to represent fractions.
* element 2 is the master (x.0) rule.
*/ */
private NFRule[] fractionRules = new NFRule[3]; LinkedList<NFRule> 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 MASTER_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 * True if the rule set is a fraction rule set. A fraction rule set
@ -72,15 +90,17 @@ final class NFRuleSet {
// construction // construction
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/* /**
* Constructs a rule set. * Constructs a rule set.
* @param owner The formatter that owns this rule set
* @param descriptions An array of Strings representing rule set * @param descriptions An array of Strings representing rule set
* descriptions. On exit, this rule set's entry in the array will * descriptions. On exit, this rule set's entry in the array will
* have been stripped of its rule set name and any trailing whitespace. * have been stripped of its rule set name and any trailing whitespace.
* @param index The index into "descriptions" of the description * @param index The index into "descriptions" of the description
* for the rule to be constructed * for the rule to be constructed
*/ */
public NFRuleSet(String[] descriptions, int index) throws IllegalArgumentException { public NFRuleSet(RuleBasedNumberFormat owner, String[] descriptions, int index) throws IllegalArgumentException {
this.owner = owner;
String description = descriptions[index]; String description = descriptions[index];
if (description.length() == 0) { if (description.length() == 0) {
@ -104,6 +124,7 @@ final class NFRuleSet {
} }
this.name = name; this.name = name;
//noinspection StatementWithEmptyBody
while (pos < description.length() && PatternProps.isWhiteSpace(description.charAt(++pos))) { while (pos < description.length() && PatternProps.isWhiteSpace(description.charAt(++pos))) {
} }
description = description.substring(pos); description = description.substring(pos);
@ -133,10 +154,8 @@ final class NFRuleSet {
* can refer to any other rule set, we have to have created all of * can refer to any other rule set, we have to have created all of
* them before we can create anything else. * them before we can create anything else.
* @param description The textual description of this rule set * @param description The textual description of this rule set
* @param owner The formatter that owns this rule set
*/ */
public void parseRules(String description, public void parseRules(String description) {
RuleBasedNumberFormat owner) {
// (the number of elements in the description list isn't necessarily // (the number of elements in the description list isn't necessarily
// the number of rules-- some descriptions may expend into two rules) // the number of rules-- some descriptions may expend into two rules)
List<NFRule> tempRules = new ArrayList<NFRule>(); List<NFRule> tempRules = new ArrayList<NFRule>();
@ -162,7 +181,9 @@ final class NFRuleSet {
// to our rule vector // to our rule vector
NFRule.makeRules(description.substring(oldP, p), NFRule.makeRules(description.substring(oldP, p),
this, predecessor, owner, tempRules); this, predecessor, owner, tempRules);
predecessor = tempRules.get(tempRules.size() - 1); if (!tempRules.isEmpty()) {
predecessor = tempRules.get(tempRules.size() - 1);
}
oldP = p + 1; oldP = p + 1;
} }
@ -174,69 +195,28 @@ final class NFRuleSet {
// rules from the list and put them into their own member variables // rules from the list and put them into their own member variables
long defaultBaseValue = 0; long defaultBaseValue = 0;
// (this isn't a for loop because we might be deleting items from for (NFRule rule : tempRules) {
// the vector-- we want to make sure we only increment i when long baseValue = rule.getBaseValue();
// we _didn't_ delete anything from the vector) if (baseValue == 0) {
int i = 0; // if the rule's base value is 0, fill in a default
while (i < tempRules.size()) { // base value (this will be 1 plus the preceding
NFRule rule = tempRules.get(i); // rule's base value for regular rule sets, and the
// same as the preceding rule's base value in fraction
switch ((int)rule.getBaseValue()) { // rule sets)
case 0: rule.setBaseValue(defaultBaseValue);
// if the rule's base value is 0, fill in a default }
// base value (this will be 1 plus the preceding else {
// rule's base value for regular rule sets, and the // if it's a regular rule that already knows its base value,
// same as the preceding rule's base value in fraction // check to make sure the rules are in order, and update
// rule sets) // the default base value for the next rule
rule.setBaseValue(defaultBaseValue); if (baseValue < defaultBaseValue) {
if (!isFractionRuleSet) { throw new IllegalArgumentException("Rules are not in order, base: " +
++defaultBaseValue; baseValue + " < " + defaultBaseValue);
} }
++i; defaultBaseValue = baseValue;
break; }
if (!isFractionRuleSet) {
case NFRule.NEGATIVE_NUMBER_RULE: ++defaultBaseValue;
// if it's the negative-number rule, copy it into its own
// data member and delete it from the list
negativeNumberRule = rule;
tempRules.remove(i);
break;
case NFRule.IMPROPER_FRACTION_RULE:
// if it's the improper fraction rule, copy it into the
// correct element of fractionRules
fractionRules[0] = rule;
tempRules.remove(i);
break;
case NFRule.PROPER_FRACTION_RULE:
// if it's the proper fraction rule, copy it into the
// correct element of fractionRules
fractionRules[1] = rule;
tempRules.remove(i);
break;
case NFRule.MASTER_RULE:
// if it's the master rule, copy it into the
// correct element of fractionRules
fractionRules[2] = rule;
tempRules.remove(i);
break;
default:
// 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 (rule.getBaseValue() < defaultBaseValue) {
throw new IllegalArgumentException("Rules are not in order, base: " +
rule.getBaseValue() + " < " + defaultBaseValue);
}
defaultBaseValue = rule.getBaseValue();
if (!isFractionRuleSet) {
++defaultBaseValue;
}
++i;
break;
} }
} }
@ -246,6 +226,60 @@ final class NFRuleSet {
tempRules.toArray(rules); 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.MASTER_RULE) {
setBestFractionRule(NFRuleSet.MASTER_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<NFRule>();
}
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 * Flags this rule set as a fraction rule set. This function is
* called during the construction process once we know this rule * called during the construction process once we know this rule
@ -276,16 +310,19 @@ final class NFRuleSet {
NFRuleSet that2 = (NFRuleSet)that; NFRuleSet that2 = (NFRuleSet)that;
if (!name.equals(that2.name) if (!name.equals(that2.name)
|| !Utility.objectEquals(negativeNumberRule, that2.negativeNumberRule)
|| !Utility.objectEquals(fractionRules[0], that2.fractionRules[0])
|| !Utility.objectEquals(fractionRules[1], that2.fractionRules[1])
|| !Utility.objectEquals(fractionRules[2], that2.fractionRules[2])
|| rules.length != that2.rules.length || rules.length != that2.rules.length
|| isFractionRuleSet != that2.isFractionRuleSet) || isFractionRuleSet != that2.isFractionRuleSet)
{ {
return false; return false;
} }
// ...then compare the non-numerical rule lists...
for (int i = 0; i < nonNumericalRules.length; i++) {
if (!Utility.objectEquals(nonNumericalRules[i], that2.nonNumericalRules[i])) {
return false;
}
}
// ...then compare the rule lists... // ...then compare the rule lists...
for (int i = 0; i < rules.length; i++) { for (int i = 0; i < rules.length; i++) {
if (!rules[i].equals(that2.rules[i])) { if (!rules[i].equals(that2.rules[i])) {
@ -317,22 +354,27 @@ final class NFRuleSet {
result.append(name).append(":\n"); result.append(name).append(":\n");
// followed by the regular rules... // followed by the regular rules...
for (int i = 0; i < rules.length; i++) { for (NFRule rule : rules) {
result.append(" ").append(rules[i].toString()).append("\n"); result.append(rule.toString()).append("\n");
} }
// followed by the special rules (if they exist) // followed by the special rules (if they exist)
if (negativeNumberRule != null) { for (NFRule rule : nonNumericalRules) {
result.append(" ").append(negativeNumberRule.toString()).append("\n"); if (rule != null) {
} if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
if (fractionRules[0] != null) { || rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
result.append(" ").append(fractionRules[0].toString()).append("\n"); || rule.getBaseValue() == NFRule.MASTER_RULE)
} {
if (fractionRules[1] != null) { for (NFRule fractionRule : fractionRules) {
result.append(" ").append(fractionRules[1].toString()).append("\n"); if (fractionRule.getBaseValue() == rule.getBaseValue()) {
} result.append(fractionRule.toString()).append("\n");
if (fractionRules[2] != null) { }
result.append(" ").append(fractionRules[2].toString()).append("\n"); }
}
else {
result.append(rule.toString()).append("\n");
}
}
} }
return result.toString(); return result.toString();
@ -387,11 +429,10 @@ final class NFRuleSet {
* this operation is to be inserted * this operation is to be inserted
*/ */
public void format(long number, StringBuffer toInsertInto, int pos, int recursionCount) { public void format(long number, StringBuffer toInsertInto, int pos, int recursionCount) {
NFRule applicableRule = findNormalRule(number);
if (recursionCount >= RECURSION_LIMIT) { if (recursionCount >= RECURSION_LIMIT) {
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name); throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
} }
NFRule applicableRule = findNormalRule(number);
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount); applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
} }
@ -404,11 +445,10 @@ final class NFRuleSet {
* this operation is to be inserted * this operation is to be inserted
*/ */
public void format(double number, StringBuffer toInsertInto, int pos, int recursionCount) { public void format(double number, StringBuffer toInsertInto, int pos, int recursionCount) {
NFRule applicableRule = findRule(number);
if (recursionCount >= RECURSION_LIMIT) { if (recursionCount >= RECURSION_LIMIT) {
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name); throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
} }
NFRule applicableRule = findRule(number);
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount); applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
} }
@ -417,42 +457,57 @@ final class NFRuleSet {
* @param number The number being formatted. * @param number The number being formatted.
* @return The rule that should be used to format it * @return The rule that should be used to format it
*/ */
private NFRule findRule(double number) { NFRule findRule(double number) {
// if this is a fraction rule set, use findFractionRuleSetRule() // if this is a fraction rule set, use findFractionRuleSetRule()
if (isFractionRuleSet) { if (isFractionRuleSet) {
return findFractionRuleSetRule(number); 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 the number is negative, return the negative number rule
// (if there isn't a negative-number rule, we pretend it's a // (if there isn't a negative-number rule, we pretend it's a
// positive number) // positive number)
if (number < 0) { if (number < 0) {
if (negativeNumberRule != null) { if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
return negativeNumberRule; return nonNumericalRules[NEGATIVE_RULE_INDEX];
} else { } else {
number = -number; number = -number;
} }
} }
// if the number isn't an integer, we use one f the fraction rules... if (Double.isInfinite(number)) {
if (number != Math.floor(number)) { NFRule rule = nonNumericalRules[INFINITY_RULE_INDEX];
// if the number is between 0 and 1, return the proper if (rule == null) {
// fraction rule rule = owner.getDefaultInfinityRule();
if (number < 1 && fractionRules[1] != null) {
return fractionRules[1];
} }
return rule;
}
// otherwise, return the improper fraction rule // if the number isn't an integer, we use one f the fraction rules...
else if (fractionRules[0] != null) { if (nonNumericalRules != null && number != Math.floor(number)) {
return fractionRules[0]; 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 master rule, use it to format the number // if there's a master rule, use it to format the number
if (fractionRules[2] != null) { if (nonNumericalRules != null && nonNumericalRules[MASTER_RULE_INDEX] != null) {
return fractionRules[2]; return nonNumericalRules[MASTER_RULE_INDEX];
}
} else { else {
// and if we haven't yet returned a rule, use findNormalRule() // and if we haven't yet returned a rule, use findNormalRule()
// to find the applicable rule // to find the applicable rule
return findNormalRule(Math.round(number)); return findNormalRule(Math.round(number));
@ -486,8 +541,8 @@ final class NFRuleSet {
// if the number is negative, return the negative-number rule // if the number is negative, return the negative-number rule
// (if there isn't one, pretend the number is positive) // (if there isn't one, pretend the number is positive)
if (number < 0) { if (number < 0) {
if (negativeNumberRule != null) { if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
return negativeNumberRule; return nonNumericalRules[NEGATIVE_RULE_INDEX];
} else { } else {
number = -number; number = -number;
} }
@ -541,7 +596,7 @@ final class NFRuleSet {
return result; return result;
} }
// else use the master rule // else use the master rule
return fractionRules[2]; return nonNumericalRules[MASTER_RULE_INDEX];
} }
/** /**
@ -697,32 +752,18 @@ final class NFRuleSet {
// that determines the value we return. // that determines the value we return.
ParsePosition highWaterMark = new ParsePosition(0); ParsePosition highWaterMark = new ParsePosition(0);
Number result = Long.valueOf(0); Number result = NFRule.ZERO;
Number tempResult = null; Number tempResult;
// dump out if there's no text to parse // dump out if there's no text to parse
if (text.length() == 0) { if (text.length() == 0) {
return result; return result;
} }
// start by trying the negative number rule (if there is one) // Try each of the negative rules, fraction rules, infinity rules and NaN rules
if (negativeNumberRule != null) { for (NFRule fractionRule : nonNumericalRules) {
tempResult = negativeNumberRule.doParse(text, parsePosition, false, upperBound); if (fractionRule != null) {
if (parsePosition.getIndex() > highWaterMark.getIndex()) { tempResult = fractionRule.doParse(text, parsePosition, false, upperBound);
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);
}
// then try each of the fraction rules
for (int i = 0; i < 3; i++) {
if (fractionRules[i] != null) {
tempResult = fractionRules[i].doParse(text, parsePosition, false, upperBound);
if (parsePosition.getIndex() > highWaterMark.getIndex()) { if (parsePosition.getIndex() > highWaterMark.getIndex()) {
result = tempResult; result = tempResult;
highWaterMark.setIndex(parsePosition.getIndex()); highWaterMark.setIndex(parsePosition.getIndex());
@ -777,5 +818,23 @@ final class NFRuleSet {
for (NFRule rule : rules) { for (NFRule rule : rules) {
rule.setDecimalFormatSymbols(newSymbols); rule.setDecimalFormatSymbols(newSymbols);
} }
// Switch the fraction rules to mirror the DecimalFormatSymbols.
if (fractionRules != null) {
for (int nonNumericalIdx = IMPROPER_FRACTION_RULE_INDEX; nonNumericalIdx <= MASTER_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);
}
}
} }
} }

View File

@ -40,11 +40,6 @@ abstract class NFSubstitution {
*/ */
final DecimalFormat numberFormat; final DecimalFormat numberFormat;
/**
* Link to the RBNF so that we can access its decimalFormat if need be.
*/
final RuleBasedNumberFormat rbnf;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// construction // construction
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -75,7 +70,7 @@ abstract class NFSubstitution {
String description) { String description) {
// if the description is empty, return a NullSubstitution // if the description is empty, return a NullSubstitution
if (description.length() == 0) { if (description.length() == 0) {
return new NullSubstitution(pos, ruleSet, formatter, description); return null;
} }
switch (description.charAt(0)) { switch (description.charAt(0)) {
@ -94,25 +89,25 @@ abstract class NFSubstitution {
|| rule.getBaseValue() == NFRule.MASTER_RULE) || rule.getBaseValue() == NFRule.MASTER_RULE)
{ {
// if the rule is a fraction rule, return an IntegralPartSubstitution // if the rule is a fraction rule, return an IntegralPartSubstitution
return new IntegralPartSubstitution(pos, ruleSet, formatter, description); return new IntegralPartSubstitution(pos, ruleSet, description);
} }
else if (ruleSet.isFractionSet()) { else if (ruleSet.isFractionSet()) {
// if the rule set containing the rule is a fraction // if the rule set containing the rule is a fraction
// rule set, return a NumeratorSubstitution // rule set, return a NumeratorSubstitution
return new NumeratorSubstitution(pos, rule.getBaseValue(), return new NumeratorSubstitution(pos, rule.getBaseValue(),
formatter.getDefaultRuleSet(), formatter, description); formatter.getDefaultRuleSet(), description);
} }
else { else {
// otherwise, return a MultiplierSubstitution // otherwise, return a MultiplierSubstitution
return new MultiplierSubstitution(pos, rule.getDivisor(), ruleSet, return new MultiplierSubstitution(pos, rule.getDivisor(), ruleSet,
formatter, description); description);
} }
case '>': case '>':
if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) { if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) {
// if the rule is a negative-number rule, return // if the rule is a negative-number rule, return
// an AbsoluteValueSubstitution // an AbsoluteValueSubstitution
return new AbsoluteValueSubstitution(pos, ruleSet, formatter, description); return new AbsoluteValueSubstitution(pos, ruleSet, description);
} }
else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE || rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
@ -120,7 +115,7 @@ abstract class NFSubstitution {
{ {
// if the rule is a fraction rule, return a // if the rule is a fraction rule, return a
// FractionalPartSubstitution // FractionalPartSubstitution
return new FractionalPartSubstitution(pos, ruleSet, formatter, description); return new FractionalPartSubstitution(pos, ruleSet, description);
} }
else if (ruleSet.isFractionSet()) { else if (ruleSet.isFractionSet()) {
// if the rule set owning the rule is a fraction rule set, // if the rule set owning the rule is a fraction rule set,
@ -135,10 +130,10 @@ abstract class NFSubstitution {
else { else {
// otherwise, return a ModulusSubstitution // otherwise, return a ModulusSubstitution
return new ModulusSubstitution(pos, rule.getDivisor(), rulePredecessor, return new ModulusSubstitution(pos, rule.getDivisor(), rulePredecessor,
ruleSet, formatter, description); ruleSet, description);
} }
case '=': case '=':
return new SameValueSubstitution(pos, ruleSet, formatter, description); return new SameValueSubstitution(pos, ruleSet, description);
default: default:
// and if it's anything else, throw an exception // and if it's anything else, throw an exception
///CLOVER:OFF ///CLOVER:OFF
@ -156,33 +151,31 @@ abstract class NFSubstitution {
* @param pos The substitution's position in the owning rule's rule * @param pos The substitution's position in the owning rule's rule
* text * text
* @param ruleSet The rule set that owns this substitution * @param ruleSet The rule set that owns this substitution
* @param formatter The RuleBasedNumberFormat that owns this substitution
* @param description The substitution descriptor (i.e., the text * @param description The substitution descriptor (i.e., the text
* inside the token characters) * inside the token characters)
*/ */
NFSubstitution(int pos, NFSubstitution(int pos,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
// initialize the substitution's position in its parent rule // initialize the substitution's position in its parent rule
this.pos = pos; this.pos = pos;
this.rbnf = formatter; int descriptionLen = description.length();
// the description should begin and end with the same character. // the description should begin and end with the same character.
// If it doesn't that's a syntax error. Otherwise, // If it doesn't that's a syntax error. Otherwise,
// makeSubstitution() was the only thing that needed to know // makeSubstitution() was the only thing that needed to know
// about these characters, so strip them off // about these characters, so strip them off
if (description.length() >= 2 && description.charAt(0) == description.charAt(description.length() - 1)) { if (descriptionLen >= 2 && description.charAt(0) == description.charAt(descriptionLen - 1)) {
description = description.substring(1, description.length() - 1); description = description.substring(1, descriptionLen - 1);
} }
else if (description.length() != 0) { else if (descriptionLen != 0) {
throw new IllegalArgumentException("Illegal substitution syntax"); throw new IllegalArgumentException("Illegal substitution syntax");
} }
// if the description was just two paired token characters // if the description was just two paired token characters
// (i.e., "<<" or ">>"), it uses the rule set it belongs to to // (i.e., "<<" or ">>"), it uses the rule set it belongs to to
// format its result // format its result
if (description.length() == 0) { if (description.isEmpty()) {
this.ruleSet = ruleSet; this.ruleSet = ruleSet;
this.numberFormat = null; this.numberFormat = null;
} }
@ -190,7 +183,7 @@ abstract class NFSubstitution {
// if the description contains a rule set name, that's the rule // if the description contains a rule set name, that's the rule
// set we use to format the result: get a reference to the // set we use to format the result: get a reference to the
// names rule set // names rule set
this.ruleSet = formatter.findRuleSet(description); this.ruleSet = ruleSet.owner.findRuleSet(description);
this.numberFormat = null; this.numberFormat = null;
} }
else if (description.charAt(0) == '#' || description.charAt(0) == '0') { else if (description.charAt(0) == '#' || description.charAt(0) == '0') {
@ -199,7 +192,8 @@ abstract class NFSubstitution {
// that pattern (then set it to use the DecimalFormatSymbols // that pattern (then set it to use the DecimalFormatSymbols
// belonging to our formatter) // belonging to our formatter)
this.ruleSet = null; this.ruleSet = null;
this.numberFormat = new DecimalFormat(description, formatter.getDecimalFormatSymbols()); this.numberFormat = (DecimalFormat) ruleSet.owner.getDecimalFormat().clone();
this.numberFormat.applyPattern(description);
} }
else if (description.charAt(0) == '>') { else if (description.charAt(0) == '>') {
// if the description is ">>>", this substitution bypasses the // if the description is ">>>", this substitution bypasses the
@ -329,6 +323,13 @@ abstract class NFSubstitution {
// is dependent on the type of substitution this is // is dependent on the type of substitution this is
double numberToFormat = transformNumber(number); 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 // if the result is an integer, from here on out we work in integer
// space (saving time and memory and preserving accuracy) // space (saving time and memory and preserving accuracy)
if (numberToFormat == Math.floor(numberToFormat) && ruleSet != null) { if (numberToFormat == Math.floor(numberToFormat) && ruleSet != null) {
@ -422,7 +423,7 @@ abstract class NFSubstitution {
if (ruleSet != null) { if (ruleSet != null) {
tempResult = ruleSet.parse(text, parsePosition, upperBound); tempResult = ruleSet.parse(text, parsePosition, upperBound);
if (lenientParse && !ruleSet.isFractionSet() && parsePosition.getIndex() == 0) { if (lenientParse && !ruleSet.isFractionSet() && parsePosition.getIndex() == 0) {
tempResult = rbnf.getDecimalFormat().parse(text, parsePosition); tempResult = ruleSet.owner.getDecimalFormat().parse(text, parsePosition);
} }
// ...or use our DecimalFormat to parse the text // ...or use our DecimalFormat to parse the text
@ -514,16 +515,6 @@ abstract class NFSubstitution {
*/ */
abstract char tokenChar(); abstract char tokenChar();
/**
* Returns true if this is a null 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 NullSubstitution
*/
public boolean isNullSubstitution() {
return false;
}
/** /**
* Returns true if this is a modulus substitution. (We didn't do this * Returns true if this is a modulus substitution. (We didn't do this
* with instanceof partially because it causes source files to * with instanceof partially because it causes source files to
@ -563,9 +554,8 @@ class SameValueSubstitution extends NFSubstitution {
*/ */
SameValueSubstitution(int pos, SameValueSubstitution(int pos,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
super(pos, ruleSet, formatter, description); super(pos, ruleSet, description);
if (description.equals("==")) { if (description.equals("==")) {
throw new IllegalArgumentException("== is not a legal token"); throw new IllegalArgumentException("== is not a legal token");
} }
@ -660,15 +650,13 @@ class MultiplierSubstitution extends NFSubstitution {
* @param pos The substitution's position in its rule's rule text * @param pos The substitution's position in its rule's rule text
* @param divisor The owning rule's divisor * @param divisor The owning rule's divisor
* @param ruleSet The ruleSet this substitution uses to format its result * @param ruleSet The ruleSet this substitution uses to format its result
* @param formatter The formatter that owns this substitution
* @param description The description describing this substitution * @param description The description describing this substitution
*/ */
MultiplierSubstitution(int pos, MultiplierSubstitution(int pos,
double divisor, double divisor,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
super(pos, ruleSet, formatter, description); super(pos, ruleSet, description);
// the owning rule's divisor affects the behavior of this // the owning rule's divisor affects the behavior of this
// substitution. Rather than keeping a back-pointer to the // substitution. Rather than keeping a back-pointer to the
@ -815,16 +803,15 @@ class ModulusSubstitution extends NFSubstitution {
* @param divisor The divisor of the rule that owns this substitution * @param divisor The divisor of the rule that owns this substitution
* @param rulePredecessor The rule that precedes this substitution's * @param rulePredecessor The rule that precedes this substitution's
* rule in its rule set's rule list * rule in its rule set's rule list
* @param formatter The RuleBasedNumberFormat owning this substitution
* @param description The description for this substitution * @param description The description for this substitution
*/ */
ModulusSubstitution(int pos, ModulusSubstitution(int pos,
double divisor, double divisor,
NFRule rulePredecessor, NFRule rulePredecessor,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter, String description)
String description) { {
super(pos, ruleSet, formatter, description); super(pos, ruleSet, description);
// the owning rule's divisor controls the behavior of this // the owning rule's divisor controls the behavior of this
// substitution: rather than keeping a backpointer to the rule, // substitution: rather than keeping a backpointer to the rule,
@ -1005,7 +992,6 @@ class ModulusSubstitution extends NFSubstitution {
* @param newRuleValue The result of parsing the substitution * @param newRuleValue The result of parsing the substitution
* @param oldRuleValue The base value of the rule containing the * @param oldRuleValue The base value of the rule containing the
* substitution * substitution
* @return (oldRuleValue - (oldRuleValue % divisor)) + newRuleValue
*/ */
public double composeRuleValue(double newRuleValue, double oldRuleValue) { public double composeRuleValue(double newRuleValue, double oldRuleValue) {
return (oldRuleValue - (oldRuleValue % divisor)) + newRuleValue; return (oldRuleValue - (oldRuleValue % divisor)) + newRuleValue;
@ -1060,9 +1046,8 @@ class IntegralPartSubstitution extends NFSubstitution {
*/ */
IntegralPartSubstitution(int pos, IntegralPartSubstitution(int pos,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
super(pos, ruleSet, formatter, description); super(pos, ruleSet, description);
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -1165,9 +1150,8 @@ class FractionalPartSubstitution extends NFSubstitution {
*/ */
FractionalPartSubstitution(int pos, FractionalPartSubstitution(int pos,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
super(pos, ruleSet, formatter, description); super(pos, ruleSet, description);
if (description.equals(">>") || description.equals(">>>") || ruleSet == this.ruleSet) { if (description.equals(">>") || description.equals(">>>") || ruleSet == this.ruleSet) {
byDigits = true; byDigits = true;
useSpaces = !description.equals(">>>"); useSpaces = !description.equals(">>>");
@ -1284,7 +1268,7 @@ class FractionalPartSubstitution extends NFSubstitution {
// nonmatching text // nonmatching text
String workText = text; String workText = text;
ParsePosition workPos = new ParsePosition(1); ParsePosition workPos = new ParsePosition(1);
double result = 0; double result;
int digit; int digit;
DigitList dl = new DigitList(); DigitList dl = new DigitList();
@ -1292,7 +1276,7 @@ class FractionalPartSubstitution extends NFSubstitution {
workPos.setIndex(0); workPos.setIndex(0);
digit = ruleSet.parse(workText, workPos, 10).intValue(); digit = ruleSet.parse(workText, workPos, 10).intValue();
if (lenientParse && workPos.getIndex() == 0) { if (lenientParse && workPos.getIndex() == 0) {
Number n = rbnf.getDecimalFormat().parse(workText, workPos); Number n = ruleSet.owner.getDecimalFormat().parse(workText, workPos);
if (n != null) { if (n != null) {
digit = n.intValue(); digit = n.intValue();
} }
@ -1366,9 +1350,8 @@ class AbsoluteValueSubstitution extends NFSubstitution {
*/ */
AbsoluteValueSubstitution(int pos, AbsoluteValueSubstitution(int pos,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
super(pos, ruleSet, formatter, description); super(pos, ruleSet, description);
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -1469,9 +1452,8 @@ class NumeratorSubstitution extends NFSubstitution {
NumeratorSubstitution(int pos, NumeratorSubstitution(int pos,
double denominator, double denominator,
NFRuleSet ruleSet, NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) { String description) {
super(pos, ruleSet, formatter, fixdesc(description)); super(pos, ruleSet, fixdesc(description));
// this substitution's behavior depends on the rule's base value // this substitution's behavior depends on the rule's base value
// Rather than keeping a backpointer to the rule, we copy its // Rather than keeping a backpointer to the rule, we copy its
@ -1672,131 +1654,3 @@ class NumeratorSubstitution extends NFSubstitution {
return '<'; return '<';
} }
} }
//===================================================================
// NullSubstitution
//===================================================================
/**
* A substitution which does nothing. This class exists just to simplify
* the logic in some other routines so that they don't have to worry
* about how many substitutions a rule has.
*/
class NullSubstitution extends NFSubstitution {
//-----------------------------------------------------------------------
// construction
//-----------------------------------------------------------------------
/**
* Constructs a NullSubstitution. This just delegates to the superclass
* constructor, but the only value we really care about is the position.
*/
NullSubstitution(int pos,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
}
//-----------------------------------------------------------------------
// boilerplate
//-----------------------------------------------------------------------
/**
* NullSubstitutions don't show up in the textual representation
* of a RuleBasedNumberFormat
*/
public String toString() {
return "";
}
//-----------------------------------------------------------------------
// formatting
//-----------------------------------------------------------------------
/**
* Does nothing.
*/
public void doSubstitution(long number, StringBuffer toInsertInto, int position) {
}
/**
* Does nothing.
*/
public void doSubstitution(double number, StringBuffer toInsertInto, int position) {
}
/**
* Never called.
*/
///CLOVER:OFF
public long transformNumber(long number) {
return 0;
}
///CLOVER:ON
/**
* Never called.
*/
///CLOVER:OFF
public double transformNumber(double number) {
return 0;
}
///CLOVER:ON
//-----------------------------------------------------------------------
// parsing
//-----------------------------------------------------------------------
/**
* Returns the partial parse result unchanged
*/
public Number doParse(String text, ParsePosition parsePosition, double baseValue,
double upperBound, boolean lenientParse) {
if (baseValue == (long)baseValue) {
return Long.valueOf((long)baseValue);
} else {
return new Double(baseValue);
}
}
/**
* Never called.
*/
///CLOVER:OFF
public double composeRuleValue(double newRuleValue, double oldRuleValue) {
return 0;
}
///CLOVER:ON
/**
* Never called.
*/
///CLOVER:OFF
public double calcUpperBound(double oldUpperBound) {
return 0;
}
///CLOVER:ON
//-----------------------------------------------------------------------
// simple accessors
//-----------------------------------------------------------------------
/**
* Returns true (this _is_ a NullSubstitution).
* @return true
*/
public boolean isNullSubstitution() {
return true;
}
/**
* Never called.
*/
///CLOVER:OFF
char tokenChar() {
return ' ';
}
///CLOVER:ON
}

View File

@ -247,17 +247,48 @@ import com.ibm.icu.util.UResourceBundleIterator;
* <tr> * <tr>
* <td width="5%" valign="top"></td> * <td width="5%" valign="top"></td>
* <td width="8%" valign="top">x.x:</td> * <td width="8%" valign="top">x.x:</td>
* <td valign="top">The rule is an <em>improper fraction rule.</em></td> * <td valign="top">The rule is an <em>improper fraction rule</em>. If the full stop in
* the middle of the rule name is replaced with the decimal point
* that is used in the language or DecimalFormatSymbols, then that rule will
* have precedence when formatting and parsing this rule. For example, some
* languages use the comma, and can thus be written as x,x instead. For example,
* you can use "x.x: &lt;&lt; point &gt;&gt;;x,x: &lt;&lt; comma &gt;&gt;;" to
* handle the decimal point that matches the language's natural spelling of
* the punctuation of either the full stop or comma.</td>
* </tr> * </tr>
* <tr> * <tr>
* <td width="5%" valign="top"></td> * <td width="5%" valign="top"></td>
* <td width="8%" valign="top">0.x:</td> * <td width="8%" valign="top">0.x:</td>
* <td valign="top">The rule is a <em>proper fraction rule.</em></td> * <td valign="top">The rule is a <em>proper fraction rule</em>. If the full stop in
* the middle of the rule name is replaced with the decimal point
* that is used in the language or DecimalFormatSymbols, then that rule will
* have precedence when formatting and parsing this rule. For example, some
* languages use the comma, and can thus be written as 0,x instead. For example,
* you can use "0.x: point &gt;&gt;;0,x: comma &gt;&gt;;" to
* handle the decimal point that matches the language's natural spelling of
* the punctuation of either the full stop or comma</td>
* </tr> * </tr>
* <tr> * <tr>
* <td width="5%" valign="top"></td> * <td width="5%" valign="top"></td>
* <td width="8%" valign="top">x.0:</td> * <td width="8%" valign="top">x.0:</td>
* <td valign="top">The rule is a <em>master rule.</em></td> * <td valign="top">The rule is a <em>master rule</em>. If the full stop in
* the middle of the rule name is replaced with the decimal point
* that is used in the language or DecimalFormatSymbols, then that rule will
* have precedence when formatting and parsing this rule. For example, some
* languages use the comma, and can thus be written as x,0 instead. For example,
* you can use "x.0: &lt;&lt; point;x,0: &lt;&lt; comma;" to
* handle the decimal point that matches the language's natural spelling of
* the punctuation of either the full stop or comma</td>
* </tr>
* <tr>
* <td width="5%" valign="top"></td>
* <td width="8%" valign="top">Inf:</td>
* <td valign="top">The rule for infinity.</td>
* </tr>
* <tr>
* <td width="5%" valign="top"></td>
* <td width="8%" valign="top">NaN:</td>
* <td valign="top">The rule for an IEEE 754 NaN (not a number).</td>
* </tr> * </tr>
* <tr> * <tr>
* <td width="5%" valign="top"></td> * <td width="5%" valign="top"></td>
@ -586,6 +617,18 @@ public class RuleBasedNumberFormat extends NumberFormat {
*/ */
private transient DecimalFormat decimalFormat = null; private transient DecimalFormat decimalFormat = null;
/**
* The rule used when dealing with infinity. This is lazy-evaluated, and derived from decimalFormat.
* It is here so it can be shared by different NFRuleSets.
*/
private transient NFRule defaultInfinityRule = null;
/**
* The rule used when dealing with IEEE 754 NaN. This is lazy-evaluated, and derived from decimalFormat.
* It is here so it can be shared by different NFRuleSets.
*/
private transient NFRule defaultNaNRule = null;
/** /**
* Flag specifying whether lenient parse mode is on or off. Off by default. * Flag specifying whether lenient parse mode is on or off. Off by default.
* @serial * @serial
@ -797,19 +840,18 @@ public class RuleBasedNumberFormat extends NumberFormat {
catch (MissingResourceException e1) { catch (MissingResourceException e1) {
} }
try { // We use findTopLevel() instead of get() because
UResourceBundle locb = bundle.get(locnames[format-1]); // it's faster when we know that it's usually going to fail.
localizations = new String[locb.getSize()][]; UResourceBundle locNamesBundle = bundle.findTopLevel(locnames[format - 1]);
if (locNamesBundle != null) {
localizations = new String[locNamesBundle.getSize()][];
for (int i = 0; i < localizations.length; ++i) { for (int i = 0; i < localizations.length; ++i) {
localizations[i] = locb.get(i).getStringArray(); localizations[i] = locNamesBundle.get(i).getStringArray();
} }
} }
catch (MissingResourceException e) { // else there are no localized names. It's not that important.
// might have description and no localizations, or no description...
}
init(description.toString(), localizations); init(description.toString(), localizations);
} }
private static final String[] rulenames = { private static final String[] rulenames = {
@ -1228,7 +1270,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
// keep track of the largest number of characters consumed in // keep track of the largest number of characters consumed in
// the various trials, and the result that corresponds to it // the various trials, and the result that corresponds to it
Number result = Long.valueOf(0); Number result = NFRule.ZERO;
ParsePosition highWaterMark = new ParsePosition(workingPos.getIndex()); ParsePosition highWaterMark = new ParsePosition(workingPos.getIndex());
// iterate over the public rule sets (beginning with the default one) // iterate over the public rule sets (beginning with the default one)
@ -1409,6 +1451,14 @@ public class RuleBasedNumberFormat extends NumberFormat {
if (decimalFormat != null) { if (decimalFormat != null) {
decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols); decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
} }
if (defaultInfinityRule != null) {
defaultInfinityRule = null;
getDefaultInfinityRule(); // Reset with the new DecimalFormatSymbols
}
if (defaultNaNRule != null) {
defaultNaNRule = null;
getDefaultNaNRule(); // Reset with the new DecimalFormatSymbols
}
// Apply the new decimalFormatSymbols by reparsing the rulesets // Apply the new decimalFormatSymbols by reparsing the rulesets
for (NFRuleSet ruleSet : ruleSets) { for (NFRuleSet ruleSet : ruleSets) {
@ -1490,11 +1540,9 @@ public class RuleBasedNumberFormat extends NumberFormat {
DecimalFormat getDecimalFormat() { DecimalFormat getDecimalFormat() {
if (decimalFormat == null) { if (decimalFormat == null) {
decimalFormat = (DecimalFormat)NumberFormat.getInstance(locale); // Don't use NumberFormat.getInstance, which can cause a recursive call
String pattern = getPattern(locale, NUMBERSTYLE);
if (decimalFormatSymbols != null) { decimalFormat = new DecimalFormat(pattern, getDecimalFormatSymbols());
decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
}
} }
return decimalFormat; return decimalFormat;
} }
@ -1503,6 +1551,28 @@ public class RuleBasedNumberFormat extends NumberFormat {
return new PluralFormat(locale, pluralType, pattern, getDecimalFormat()); return new PluralFormat(locale, pluralType, pattern, getDecimalFormat());
} }
/**
* Returns the default rule for infinity. This object is lazily created: this function
* creates it the first time it's called.
*/
NFRule getDefaultInfinityRule() {
if (defaultInfinityRule == null) {
defaultInfinityRule = new NFRule(this, "Inf: " + getDecimalFormatSymbols().getInfinity());
}
return defaultInfinityRule;
}
/**
* Returns the default rule for NaN. This object is lazily created: this function
* creates it the first time it's called.
*/
NFRule getDefaultNaNRule() {
if (defaultNaNRule == null) {
defaultNaNRule = new NFRule(this, "NaN: " + getDecimalFormatSymbols().getNaN());
}
return defaultNaNRule;
}
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// construction implementation // construction implementation
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -1613,7 +1683,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
p = descBuf.length() - 1; p = descBuf.length() - 1;
} }
ruleSetDescriptions[curRuleSet] = descBuf.substring(start, p + 1); ruleSetDescriptions[curRuleSet] = descBuf.substring(start, p + 1);
NFRuleSet ruleSet = new NFRuleSet(ruleSetDescriptions, curRuleSet); NFRuleSet ruleSet = new NFRuleSet(this, ruleSetDescriptions, curRuleSet);
ruleSets[curRuleSet] = ruleSet; ruleSets[curRuleSet] = ruleSet;
String currentName = ruleSet.getName(); String currentName = ruleSet.getName();
ruleSetsMap.put(currentName, ruleSet); ruleSetsMap.put(currentName, ruleSet);
@ -1659,7 +1729,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
// finally, we can go back through the temporary descriptions // finally, we can go back through the temporary descriptions
// list and finish setting up the substructure // list and finish setting up the substructure
for (int i = 0; i < ruleSets.length; i++) { for (int i = 0; i < ruleSets.length; i++) {
ruleSets[i].parseRules(ruleSetDescriptions[i], this); ruleSets[i].parseRules(ruleSetDescriptions[i]);
} }
// Now that the rules are initialized, the 'real' default rule // Now that the rules are initialized, the 'real' default rule

View File

@ -1487,4 +1487,72 @@ public class RbnfTest extends TestFmwk {
} }
} }
public void TestInfinityNaN() {
String enRules = "%default:"
+ "-x: minus >>;"
+ "Inf: infinite;"
+ "NaN: not a number;"
+ "0: =#,##0=;";
RuleBasedNumberFormat enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
String[][] enTestData = {
{"1", "1"},
{"\u221E", "infinite"},
{"-\u221E", "minus infinite"},
{"NaN", "not a number"},
};
doTest(enFormatter, enTestData, true);
// Test the default behavior when the rules are undefined.
enRules = "%default:"
+ "-x: ->>;"
+ "0: =#,##0=;";
enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
String[][] enDefaultTestData = {
{"1", "1"},
{"\u221E", ""},
{"-\u221E", "-∞"},
{"NaN", "NaN"},
};
doTest(enFormatter, enDefaultTestData, true);
}
public void TestVariableDecimalPoint() {
String enRules = "%spellout-numbering:"
+ "-x: minus >>;"
+ "x.x: << point >>;"
+ "x,x: << comma >>;"
+ "0.x: xpoint >>;"
+ "0,x: xcomma >>;"
+ "0: zero;"
+ "1: one;"
+ "2: two;"
+ "3: three;"
+ "4: four;"
+ "5: five;"
+ "6: six;"
+ "7: seven;"
+ "8: eight;"
+ "9: nine;";
RuleBasedNumberFormat enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
String[][] enTestPointData = {
{"1.1", "one point one"},
{"1.23", "one point two three"},
{"0.4", "xpoint four"},
};
doTest(enFormatter, enTestPointData, true);
DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(ULocale.ENGLISH);
decimalFormatSymbols.setDecimalSeparator(',');
enFormatter.setDecimalFormatSymbols(decimalFormatSymbols);
String[][] enTestCommaData = {
{"1.1", "one comma one"},
{"1.23", "one comma two three"},
{"0.4", "xcomma four"},
};
doTest(enFormatter, enTestCommaData, true);
}
} }