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. *
*******************************************************************************
*/
@ -697,7 +697,11 @@ public class DecimalFormat extends NumberFormat {
private void createFromPatternAndSymbols(String pattern, DecimalFormatSymbols inputSymbols) {
// Always applyPattern after the symbols are set
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);
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
@ -2332,7 +2336,7 @@ public class DecimalFormat extends NumberFormat {
* @param negSuffix negative suffix pattern
* @param posPrefix positive prefix 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.
*/
private final boolean subparse(
@ -2779,7 +2783,7 @@ public class DecimalFormat extends NumberFormat {
* @param isNegative
* @param isPrefix
* @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 currency return value for parsed currency, for generic currency parsing
* 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 com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.Utility;
/**
* 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
*/
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
*/
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
*/
public static final int PROPER_FRACTION_RULE = -3;
static final int PROPER_FRACTION_RULE = -3;
/**
* 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
@ -63,6 +76,11 @@ final class NFRule {
*/
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
* 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
// (this also strips the rule descriptor, if any, off the
// description string)
NFRule rule1 = new NFRule(ownersOwner);
description = rule1.parseRuleDescriptor(description);
NFRule rule1 = new NFRule(ownersOwner, description);
description = rule1.ruleText;
// check the description to see whether there's text enclosed
// in brackets
int brack1 = description.indexOf("[");
int brack2 = description.indexOf("]");
int brack1 = description.indexOf('[');
int brack2 = brack1 < 0 ? -1 : description.indexOf(']');
// if the description doesn't contain a matched pair of brackets,
// or if it's of a type that doesn't recognize bracketed text,
// then leave the description alone, initialize the rule's
// rule text and substitutions, and return that rule
if (brack1 == -1 || brack2 == -1 || brack1 > brack2
|| rule1.getBaseValue() == PROPER_FRACTION_RULE
|| rule1.getBaseValue() == NEGATIVE_NUMBER_RULE)
if (brack2 < 0 || brack1 > brack2
|| rule1.baseValue == PROPER_FRACTION_RULE
|| rule1.baseValue == NEGATIVE_NUMBER_RULE
|| rule1.baseValue == INFINITY_RULE
|| rule1.baseValue == NAN_RULE)
{
rule1.extractSubstitutions(owner, description, predecessor);
}
@ -155,7 +175,7 @@ final class NFRule {
// set, they both have the same base value; otherwise,
// increment the original rule's base value ("rule1" actually
// goes SECOND in the rule set's rule list)
rule2 = new NFRule(ownersOwner);
rule2 = new NFRule(ownersOwner, null);
if (rule1.baseValue >= 0) {
rule2.baseValue = rule1.baseValue;
if (!owner.isFractionSet()) {
@ -207,18 +227,29 @@ final class NFRule {
// material in the brackets and rule1 INCLUDES the material
// in the brackets)
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
* an NFRule is actually performed by makeRules().
*/
public NFRule(RuleBasedNumberFormat formatter) {
public NFRule(RuleBasedNumberFormat formatter, String ruleText) {
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
// it's omitted, just set the base value to 0.
int p = description.indexOf(":");
if (p == -1) {
setBaseValue(0);
}
else {
if (p != -1) {
// copy the descriptor out into its own string and strip it,
// along with any trailing whitespace, out of the original
// description
@ -257,14 +285,13 @@ final class NFRule {
// check first to see if the rule descriptor matches the token
// for one of the special rules. If it does, set the base
// value to the correct identifier value
if (descriptor.equals("0.x")) {
setBaseValue(PROPER_FRACTION_RULE);
}
else if (descriptor.charAt(0) >= '0' && descriptor.charAt(0) <= '9') {
int descriptorLength = descriptor.length();
char firstChar = descriptor.charAt(0);
char lastChar = descriptor.charAt(descriptorLength - 1);
if (firstChar >= '0' && firstChar <= '9' && lastChar != 'x') {
// if the rule descriptor begins with a digit, it's a descriptor
// for a normal rule
long tempValue = 0;
int descriptorLength = descriptor.length();
char c = 0;
p = 0;
@ -339,13 +366,28 @@ final class NFRule {
else if (descriptor.equals("-x")) {
setBaseValue(NEGATIVE_NUMBER_RULE);
}
else if (descriptor.equals("x.x")) {
setBaseValue(IMPROPER_FRACTION_RULE);
}
else if (descriptor.equals("x.0")) {
setBaseValue(MASTER_RULE);
else if (descriptorLength == 3) {
if (firstChar == '0' && lastChar == 'x') {
setBaseValue(PROPER_FRACTION_RULE);
decimalPoint = descriptor.charAt(1);
}
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
// (this is generally used to put whitespace at the beginning of
@ -371,11 +413,10 @@ final class NFRule {
String ruleText,
NFRule predecessor) {
this.ruleText = ruleText;
this.rulePatternFormat = null;
sub1 = extractSubstitution(owner, predecessor);
if (sub1.isNullSubstitution()) {
if (sub1 == null) {
// Small optimization. There is no need to create a redundant NullSubstitution.
sub2 = sub1;
sub2 = null;
}
else {
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,
* 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
// a substitution token
subStart = indexOfAny(ruleText, RULE_PREFIXES);
subStart = indexOfAnyRulePrefix(ruleText);
// if we didn't find one, create a null substitution positioned
// at the end of the rule text
if (subStart == -1) {
return NFSubstitution.makeSubstitution(ruleText.length(), this, predecessor,
owner, this.formatter, "");
return null;
}
// special-case the ">>>" token, since searching for the > at the
// end will actually find the > in the middle
if (ruleText.substring(subStart).startsWith(">>>")) {
if (ruleText.startsWith(">>>", subStart)) {
subEnd = subStart + 2;
}
else {
@ -462,8 +496,7 @@ final class NFRule {
// unmatched token character), create a null substitution positioned
// at the end of the rule
if (subEnd == -1) {
return NFSubstitution.makeSubstitution(ruleText.length(), this, predecessor,
owner, this.formatter, "");
return null;
}
// 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) {
// set the base value
baseValue = newBaseValue;
radix = 10;
// 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
@ -494,7 +528,6 @@ final class NFRule {
// recalculated again-- the only function that does this is
// NFRule.parseRuleDescriptor() )
if (baseValue >= 1) {
radix = 10;
exponent = expectedExponent();
// this function gets called on a fully-constructed rule whose
@ -511,7 +544,6 @@ final class NFRule {
else {
// if this is a special rule, its radix and exponent are basically
// ignored. Set them to "safe" default values
radix = 10;
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.
* @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
* (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
* "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;
if (ruleText.length() > 0) {
int pos;
for (String string : strings) {
for (String string : RULE_PREFIXES) {
pos = ruleText.indexOf(string);
if (pos != -1 && (result == -1 || pos < result)) {
result = pos;
@ -580,8 +616,8 @@ final class NFRule {
&& radix == that2.radix
&& exponent == that2.exponent
&& ruleText.equals(that2.ruleText)
&& sub1.equals(that2.sub1)
&& sub2.equals(that2.sub2);
&& Utility.objectEquals(sub1, that2.sub1)
&& Utility.objectEquals(sub2, that2.sub2);
}
return false;
}
@ -605,13 +641,19 @@ final class NFRule {
result.append("-x: ");
}
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) {
result.append("0.x: ");
result.append('0').append(decimalPoint == 0 ? '.' : decimalPoint).append("x: ");
}
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 {
// 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
// apostrophe is used to make the whitespace significant)
if (ruleText.startsWith(" ") && (sub1 == null || sub1.getPos() != 0)) {
result.append("\'");
result.append('\'');
}
// now, write the rule's rule text, inserting appropriate
// substitution tokens in the appropriate places
StringBuilder ruleTextCopy = new StringBuilder(ruleText);
ruleTextCopy.insert(sub2.getPos(), sub2.toString());
ruleTextCopy.insert(sub1.getPos(), sub1.toString());
if (sub2 != null) {
ruleTextCopy.insert(sub2.getPos(), sub2.toString());
}
if (sub1 != null) {
ruleTextCopy.insert(sub1.getPos(), sub1.toString());
}
result.append(ruleTextCopy.toString());
// and finally, top the whole thing off with a semicolon and
@ -654,6 +700,14 @@ final class NFRule {
// 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
* @return The rule's base value
@ -708,10 +762,10 @@ final class NFRule {
}
lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength);
}
if (!sub2.isNullSubstitution()) {
if (sub2 != null) {
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);
}
}
@ -750,10 +804,10 @@ final class NFRule {
}
lengthOffset = ruleText.length() - (toInsertInto.length() - initialLength);
}
if (!sub2.isNullSubstitution()) {
if (sub2 != null) {
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);
}
}
@ -783,7 +837,7 @@ final class NFRule {
// a modulus substitution, its base value isn't an even multiple
// of 100, and the value we're trying to format _is_ an even
// 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;
}
@ -820,13 +874,25 @@ final class NFRule {
// matches the text at the beginning of the string being
// parsed. If it does, strip that off the front of workText;
// 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();
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
// 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
@ -870,14 +936,14 @@ final class NFRule {
// the substitution, giving us a new partial parse result
pp.setIndex(0);
double partialResult = matchToDelimiter(workText, start, tempBaseValue,
ruleText.substring(sub1.getPos(), sub2.getPos()), rulePatternFormat,
ruleText.substring(sub1Pos, sub2Pos), rulePatternFormat,
pp, sub1, upperBound).doubleValue();
// if we got a successful match (or were trying to match a
// null substitution), pp is now pointing at the first unmatched
// character. Take note of that, and try matchToDelimiter()
// on the input text again
if (pp.getIndex() != 0 || sub1.isNullSubstitution()) {
if (pp.getIndex() != 0 || sub1 == null) {
start = pp.getIndex();
String workText2 = workText.substring(pp.getIndex());
@ -888,13 +954,13 @@ final class NFRule {
// substitution if there's a successful match, giving us
// a real result
partialResult = matchToDelimiter(workText2, 0, partialResult,
ruleText.substring(sub2.getPos()), rulePatternFormat, pp2, sub2,
ruleText.substring(sub2Pos), rulePatternFormat, pp2, sub2,
upperBound).doubleValue();
// if we got a successful match on this second
// matchToDelimiter() call, update the high-water mark
// and result (if necessary)
if (pp2.getIndex() != 0 || sub2.isNullSubstitution()) {
if (pp2.getIndex() != 0 || sub2 == null) {
if (prefixLength + pp.getIndex() + pp2.getIndex() > highWaterMark) {
highWaterMark = prefixLength + pp.getIndex() + pp2.getIndex();
result = partialResult;
@ -918,7 +984,8 @@ final class NFRule {
// keep trying to match things until the outer matchToDelimiter()
// call fails to make a match (each time, it picks up where it
// 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);
// 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
// rule in a fraction rule set has no substitutions, its numerator
// 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;
}
@ -1071,21 +1138,23 @@ final class NFRule {
// if we make it here, this was an unsuccessful match, and we
// leave pp unchanged and return 0
pp.setIndex(0);
return Long.valueOf(0);
return ZERO;
// if "delimiter" is empty, or consists only of ignorable characters
// (i.e., is semantically empty), thwe we obviously can't search
// for "delimiter". Instead, just use "sub" to parse as much of
// "text" as possible.
} else {
}
else if (sub == null) {
return baseVal;
}
else {
ParsePosition tempPP = new ParsePosition(0);
Number result = Long.valueOf(0);
Number tempResult;
Number result = ZERO;
// try to match the whole string against the substitution
tempResult = sub.doParse(text, tempPP, baseVal, upperBound,
formatter.lenientParseEnabled());
if (tempPP.getIndex() != 0 || sub.isNullSubstitution()) {
Number tempResult = sub.doParse(text, tempPP, baseVal, upperBound,
formatter.lenientParseEnabled());
if (tempPP.getIndex() != 0) {
// if there's a successful match (or it's a null
// substitution), update pp to point to the first
// 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.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.ibm.icu.impl.PatternProps;
@ -37,16 +38,33 @@ final class NFRuleSet {
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
* (0.x) rule, element 1 is the improper fraction (x.x) rule, and
* element 2 is the master (x.0) rule.
* These are a pile of fraction rules in declared order. They may have alternate
* ways to represent fractions.
*/
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
@ -72,15 +90,17 @@ final class NFRuleSet {
// 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(String[] descriptions, int index) throws IllegalArgumentException {
public NFRuleSet(RuleBasedNumberFormat owner, String[] descriptions, int index) throws IllegalArgumentException {
this.owner = owner;
String description = descriptions[index];
if (description.length() == 0) {
@ -104,6 +124,7 @@ final class NFRuleSet {
}
this.name = name;
//noinspection StatementWithEmptyBody
while (pos < description.length() && PatternProps.isWhiteSpace(description.charAt(++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
* them before we can create anything else.
* @param description The textual description of this rule set
* @param owner The formatter that owns this rule set
*/
public void parseRules(String description,
RuleBasedNumberFormat owner) {
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<NFRule> tempRules = new ArrayList<NFRule>();
@ -162,7 +181,9 @@ final class NFRuleSet {
// to our rule vector
NFRule.makeRules(description.substring(oldP, p),
this, predecessor, owner, tempRules);
predecessor = tempRules.get(tempRules.size() - 1);
if (!tempRules.isEmpty()) {
predecessor = tempRules.get(tempRules.size() - 1);
}
oldP = p + 1;
}
@ -174,69 +195,28 @@ final class NFRuleSet {
// rules from the list and put them into their own member variables
long defaultBaseValue = 0;
// (this isn't a for loop because we might be deleting items from
// the vector-- we want to make sure we only increment i when
// we _didn't_ delete anything from the vector)
int i = 0;
while (i < tempRules.size()) {
NFRule rule = tempRules.get(i);
switch ((int)rule.getBaseValue()) {
case 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);
if (!isFractionRuleSet) {
++defaultBaseValue;
}
++i;
break;
case NFRule.NEGATIVE_NUMBER_RULE:
// 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;
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;
}
}
@ -246,6 +226,60 @@ final class NFRuleSet {
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
* called during the construction process once we know this rule
@ -276,16 +310,19 @@ final class NFRuleSet {
NFRuleSet that2 = (NFRuleSet)that;
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
|| isFractionRuleSet != that2.isFractionRuleSet)
{
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...
for (int i = 0; i < rules.length; i++) {
if (!rules[i].equals(that2.rules[i])) {
@ -317,22 +354,27 @@ final class NFRuleSet {
result.append(name).append(":\n");
// followed by the regular rules...
for (int i = 0; i < rules.length; i++) {
result.append(" ").append(rules[i].toString()).append("\n");
for (NFRule rule : rules) {
result.append(rule.toString()).append("\n");
}
// followed by the special rules (if they exist)
if (negativeNumberRule != null) {
result.append(" ").append(negativeNumberRule.toString()).append("\n");
}
if (fractionRules[0] != null) {
result.append(" ").append(fractionRules[0].toString()).append("\n");
}
if (fractionRules[1] != null) {
result.append(" ").append(fractionRules[1].toString()).append("\n");
}
if (fractionRules[2] != null) {
result.append(" ").append(fractionRules[2].toString()).append("\n");
for (NFRule rule : nonNumericalRules) {
if (rule != null) {
if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.MASTER_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();
@ -387,11 +429,10 @@ final class NFRuleSet {
* this operation is to be inserted
*/
public void format(long number, StringBuffer toInsertInto, int pos, int recursionCount) {
NFRule applicableRule = findNormalRule(number);
if (recursionCount >= RECURSION_LIMIT) {
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
}
NFRule applicableRule = findNormalRule(number);
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
}
@ -404,11 +445,10 @@ final class NFRuleSet {
* this operation is to be inserted
*/
public void format(double number, StringBuffer toInsertInto, int pos, int recursionCount) {
NFRule applicableRule = findRule(number);
if (recursionCount >= RECURSION_LIMIT) {
throw new IllegalStateException("Recursion limit exceeded when applying ruleSet " + name);
}
NFRule applicableRule = findRule(number);
applicableRule.doFormat(number, toInsertInto, pos, ++recursionCount);
}
@ -417,42 +457,57 @@ final class NFRuleSet {
* @param number The number being formatted.
* @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 (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 (negativeNumberRule != null) {
return negativeNumberRule;
if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
return nonNumericalRules[NEGATIVE_RULE_INDEX];
} else {
number = -number;
}
}
// if the number isn't an integer, we use one f the fraction rules...
if (number != Math.floor(number)) {
// if the number is between 0 and 1, return the proper
// fraction rule
if (number < 1 && fractionRules[1] != null) {
return fractionRules[1];
if (Double.isInfinite(number)) {
NFRule rule = nonNumericalRules[INFINITY_RULE_INDEX];
if (rule == null) {
rule = owner.getDefaultInfinityRule();
}
return rule;
}
// otherwise, return the improper fraction rule
else if (fractionRules[0] != null) {
return fractionRules[0];
// if the number isn't an integer, we use one f the fraction rules...
if (nonNumericalRules != null && 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 master rule, use it to format the number
if (fractionRules[2] != null) {
return fractionRules[2];
} else {
if (nonNumericalRules != null && nonNumericalRules[MASTER_RULE_INDEX] != null) {
return nonNumericalRules[MASTER_RULE_INDEX];
}
else {
// and if we haven't yet returned a rule, use findNormalRule()
// to find the applicable rule
return findNormalRule(Math.round(number));
@ -486,8 +541,8 @@ final class NFRuleSet {
// if the number is negative, return the negative-number rule
// (if there isn't one, pretend the number is positive)
if (number < 0) {
if (negativeNumberRule != null) {
return negativeNumberRule;
if (nonNumericalRules[NEGATIVE_RULE_INDEX] != null) {
return nonNumericalRules[NEGATIVE_RULE_INDEX];
} else {
number = -number;
}
@ -541,7 +596,7 @@ final class NFRuleSet {
return result;
}
// 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.
ParsePosition highWaterMark = new ParsePosition(0);
Number result = Long.valueOf(0);
Number tempResult = null;
Number result = NFRule.ZERO;
Number tempResult;
// dump out if there's no text to parse
if (text.length() == 0) {
return result;
}
// start by trying the negative number rule (if there is one)
if (negativeNumberRule != null) {
tempResult = negativeNumberRule.doParse(text, parsePosition, false, upperBound);
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);
}
// 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);
// Try each of the negative rules, fraction rules, infinity rules and NaN rules
for (NFRule fractionRule : nonNumericalRules) {
if (fractionRule != null) {
tempResult = fractionRule.doParse(text, parsePosition, false, upperBound);
if (parsePosition.getIndex() > highWaterMark.getIndex()) {
result = tempResult;
highWaterMark.setIndex(parsePosition.getIndex());
@ -777,5 +818,23 @@ final class NFRuleSet {
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 <= 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;
/**
* Link to the RBNF so that we can access its decimalFormat if need be.
*/
final RuleBasedNumberFormat rbnf;
//-----------------------------------------------------------------------
// construction
//-----------------------------------------------------------------------
@ -75,7 +70,7 @@ abstract class NFSubstitution {
String description) {
// if the description is empty, return a NullSubstitution
if (description.length() == 0) {
return new NullSubstitution(pos, ruleSet, formatter, description);
return null;
}
switch (description.charAt(0)) {
@ -94,25 +89,25 @@ abstract class NFSubstitution {
|| rule.getBaseValue() == NFRule.MASTER_RULE)
{
// 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()) {
// if the rule set containing the rule is a fraction
// rule set, return a NumeratorSubstitution
return new NumeratorSubstitution(pos, rule.getBaseValue(),
formatter.getDefaultRuleSet(), formatter, description);
formatter.getDefaultRuleSet(), description);
}
else {
// otherwise, return a MultiplierSubstitution
return new MultiplierSubstitution(pos, rule.getDivisor(), ruleSet,
formatter, description);
description);
}
case '>':
if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) {
// if the rule is a negative-number rule, return
// an AbsoluteValueSubstitution
return new AbsoluteValueSubstitution(pos, ruleSet, formatter, description);
return new AbsoluteValueSubstitution(pos, ruleSet, description);
}
else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
@ -120,7 +115,7 @@ abstract class NFSubstitution {
{
// if the rule is a fraction rule, return a
// FractionalPartSubstitution
return new FractionalPartSubstitution(pos, ruleSet, formatter, description);
return new FractionalPartSubstitution(pos, ruleSet, description);
}
else if (ruleSet.isFractionSet()) {
// if the rule set owning the rule is a fraction rule set,
@ -135,10 +130,10 @@ abstract class NFSubstitution {
else {
// otherwise, return a ModulusSubstitution
return new ModulusSubstitution(pos, rule.getDivisor(), rulePredecessor,
ruleSet, formatter, description);
ruleSet, description);
}
case '=':
return new SameValueSubstitution(pos, ruleSet, formatter, description);
return new SameValueSubstitution(pos, ruleSet, description);
default:
// and if it's anything else, throw an exception
///CLOVER:OFF
@ -156,33 +151,31 @@ abstract class NFSubstitution {
* @param pos The substitution's position in the owning rule's rule
* text
* @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
* inside the token characters)
*/
NFSubstitution(int pos,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
// initialize the substitution's position in its parent rule
this.pos = pos;
this.rbnf = formatter;
int descriptionLen = description.length();
// the description should begin and end with the same character.
// If it doesn't that's a syntax error. Otherwise,
// makeSubstitution() was the only thing that needed to know
// about these characters, so strip them off
if (description.length() >= 2 && description.charAt(0) == description.charAt(description.length() - 1)) {
description = description.substring(1, description.length() - 1);
if (descriptionLen >= 2 && description.charAt(0) == description.charAt(descriptionLen - 1)) {
description = description.substring(1, descriptionLen - 1);
}
else if (description.length() != 0) {
else if (descriptionLen != 0) {
throw new IllegalArgumentException("Illegal substitution syntax");
}
// if the description was just two paired token characters
// (i.e., "<<" or ">>"), it uses the rule set it belongs to to
// format its result
if (description.length() == 0) {
if (description.isEmpty()) {
this.ruleSet = ruleSet;
this.numberFormat = null;
}
@ -190,7 +183,7 @@ abstract class NFSubstitution {
// if the description contains a rule set name, that's the rule
// set we use to format the result: get a reference to the
// names rule set
this.ruleSet = formatter.findRuleSet(description);
this.ruleSet = ruleSet.owner.findRuleSet(description);
this.numberFormat = null;
}
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
// belonging to our formatter)
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) == '>') {
// if the description is ">>>", this substitution bypasses the
@ -329,6 +323,13 @@ abstract class NFSubstitution {
// is dependent on the type of substitution this is
double numberToFormat = transformNumber(number);
if (Double.isInfinite(numberToFormat)) {
// This is probably a minus rule. Combine it with an infinite rule.
NFRule infiniteRule = ruleSet.findRule(Double.POSITIVE_INFINITY);
infiniteRule.doFormat(numberToFormat, toInsertInto, position + pos, recursionCount);
return;
}
// if the result is an integer, from here on out we work in integer
// space (saving time and memory and preserving accuracy)
if (numberToFormat == Math.floor(numberToFormat) && ruleSet != null) {
@ -422,7 +423,7 @@ abstract class NFSubstitution {
if (ruleSet != null) {
tempResult = ruleSet.parse(text, parsePosition, upperBound);
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
@ -514,16 +515,6 @@ abstract class NFSubstitution {
*/
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
* with instanceof partially because it causes source files to
@ -563,9 +554,8 @@ class SameValueSubstitution extends NFSubstitution {
*/
SameValueSubstitution(int pos,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
super(pos, ruleSet, description);
if (description.equals("==")) {
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 divisor The owning rule's divisor
* @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
*/
MultiplierSubstitution(int pos,
double divisor,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
super(pos, ruleSet, description);
// the owning rule's divisor affects the behavior of this
// 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 rulePredecessor The rule that precedes this substitution's
* rule in its rule set's rule list
* @param formatter The RuleBasedNumberFormat owning this substitution
* @param description The description for this substitution
*/
ModulusSubstitution(int pos,
double divisor,
NFRule rulePredecessor,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
String description)
{
super(pos, ruleSet, description);
// the owning rule's divisor controls the behavior of this
// substitution: rather than keeping a backpointer to the rule,
@ -1005,7 +992,6 @@ class ModulusSubstitution extends NFSubstitution {
* @param newRuleValue The result of parsing the substitution
* @param oldRuleValue The base value of the rule containing the
* substitution
* @return (oldRuleValue - (oldRuleValue % divisor)) + newRuleValue
*/
public double composeRuleValue(double newRuleValue, double oldRuleValue) {
return (oldRuleValue - (oldRuleValue % divisor)) + newRuleValue;
@ -1060,9 +1046,8 @@ class IntegralPartSubstitution extends NFSubstitution {
*/
IntegralPartSubstitution(int pos,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
super(pos, ruleSet, description);
}
//-----------------------------------------------------------------------
@ -1165,9 +1150,8 @@ class FractionalPartSubstitution extends NFSubstitution {
*/
FractionalPartSubstitution(int pos,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
super(pos, ruleSet, description);
if (description.equals(">>") || description.equals(">>>") || ruleSet == this.ruleSet) {
byDigits = true;
useSpaces = !description.equals(">>>");
@ -1284,7 +1268,7 @@ class FractionalPartSubstitution extends NFSubstitution {
// nonmatching text
String workText = text;
ParsePosition workPos = new ParsePosition(1);
double result = 0;
double result;
int digit;
DigitList dl = new DigitList();
@ -1292,7 +1276,7 @@ class FractionalPartSubstitution extends NFSubstitution {
workPos.setIndex(0);
digit = ruleSet.parse(workText, workPos, 10).intValue();
if (lenientParse && workPos.getIndex() == 0) {
Number n = rbnf.getDecimalFormat().parse(workText, workPos);
Number n = ruleSet.owner.getDecimalFormat().parse(workText, workPos);
if (n != null) {
digit = n.intValue();
}
@ -1366,9 +1350,8 @@ class AbsoluteValueSubstitution extends NFSubstitution {
*/
AbsoluteValueSubstitution(int pos,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
super(pos, ruleSet, description);
}
//-----------------------------------------------------------------------
@ -1469,9 +1452,8 @@ class NumeratorSubstitution extends NFSubstitution {
NumeratorSubstitution(int pos,
double denominator,
NFRuleSet ruleSet,
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, fixdesc(description));
super(pos, ruleSet, fixdesc(description));
// this substitution's behavior depends on the rule's base value
// Rather than keeping a backpointer to the rule, we copy its
@ -1672,131 +1654,3 @@ class NumeratorSubstitution extends NFSubstitution {
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>
* <td width="5%" valign="top"></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>
* <td width="5%" valign="top"></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>
* <td width="5%" valign="top"></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>
* <td width="5%" valign="top"></td>
@ -586,6 +617,18 @@ public class RuleBasedNumberFormat extends NumberFormat {
*/
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.
* @serial
@ -797,19 +840,18 @@ public class RuleBasedNumberFormat extends NumberFormat {
catch (MissingResourceException e1) {
}
try {
UResourceBundle locb = bundle.get(locnames[format-1]);
localizations = new String[locb.getSize()][];
// We use findTopLevel() instead of get() because
// it's faster when we know that it's usually going to fail.
UResourceBundle locNamesBundle = bundle.findTopLevel(locnames[format - 1]);
if (locNamesBundle != null) {
localizations = new String[locNamesBundle.getSize()][];
for (int i = 0; i < localizations.length; ++i) {
localizations[i] = locb.get(i).getStringArray();
localizations[i] = locNamesBundle.get(i).getStringArray();
}
}
catch (MissingResourceException e) {
// might have description and no localizations, or no description...
}
// else there are no localized names. It's not that important.
init(description.toString(), localizations);
}
private static final String[] rulenames = {
@ -1228,7 +1270,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
// keep track of the largest number of characters consumed in
// 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());
// iterate over the public rule sets (beginning with the default one)
@ -1409,7 +1451,15 @@ public class RuleBasedNumberFormat extends NumberFormat {
if (decimalFormat != null) {
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
for (NFRuleSet ruleSet : ruleSets) {
ruleSet.setDecimalFormatSymbols(decimalFormatSymbols);
@ -1490,11 +1540,9 @@ public class RuleBasedNumberFormat extends NumberFormat {
DecimalFormat getDecimalFormat() {
if (decimalFormat == null) {
decimalFormat = (DecimalFormat)NumberFormat.getInstance(locale);
if (decimalFormatSymbols != null) {
decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
}
// Don't use NumberFormat.getInstance, which can cause a recursive call
String pattern = getPattern(locale, NUMBERSTYLE);
decimalFormat = new DecimalFormat(pattern, getDecimalFormatSymbols());
}
return decimalFormat;
}
@ -1503,6 +1551,28 @@ public class RuleBasedNumberFormat extends NumberFormat {
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
//-----------------------------------------------------------------------
@ -1613,7 +1683,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
p = descBuf.length() - 1;
}
ruleSetDescriptions[curRuleSet] = descBuf.substring(start, p + 1);
NFRuleSet ruleSet = new NFRuleSet(ruleSetDescriptions, curRuleSet);
NFRuleSet ruleSet = new NFRuleSet(this, ruleSetDescriptions, curRuleSet);
ruleSets[curRuleSet] = ruleSet;
String currentName = ruleSet.getName();
ruleSetsMap.put(currentName, ruleSet);
@ -1659,7 +1729,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
// finally, we can go back through the temporary descriptions
// list and finish setting up the substructure
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

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);
}
}