diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index ebee41c908c..804ded315c4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -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 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NFRule.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NFRule.java index d29145c9645..243e799729c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NFRule.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NFRule.java @@ -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 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NFRuleSet.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NFRuleSet.java index 8fd141190cb..9e9b6bb9b4d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NFRuleSet.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NFRuleSet.java @@ -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 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 tempRules = new ArrayList(); @@ -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(); + } + 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); + } + } } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java index 3fc06bae60b..e63aa7fd0a0 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NFSubstitution.java @@ -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 -} - diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/RuleBasedNumberFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/RuleBasedNumberFormat.java index 29de0ae501b..cab35dfbbd3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/RuleBasedNumberFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/RuleBasedNumberFormat.java @@ -247,17 +247,48 @@ import com.ibm.icu.util.UResourceBundleIterator; * * * x.x: - * The rule is an improper fraction rule. + * The rule is an improper fraction rule. 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: << point >>;x,x: << comma >>;" to + * handle the decimal point that matches the language's natural spelling of + * the punctuation of either the full stop or comma. * * * * 0.x: - * The rule is a proper fraction rule. + * The rule is a proper fraction rule. 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 >>;0,x: comma >>;" to + * handle the decimal point that matches the language's natural spelling of + * the punctuation of either the full stop or comma * * * * x.0: - * The rule is a master rule. + * The rule is a master rule. 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: << point;x,0: << comma;" to + * handle the decimal point that matches the language's natural spelling of + * the punctuation of either the full stop or comma + * + * + * + * Inf: + * The rule for infinity. + * + * + * + * NaN: + * The rule for an IEEE 754 NaN (not a number). * * * @@ -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 diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java index cced293d99f..8159ffee05e 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RbnfTest.java @@ -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); + } + }