diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java index 32fc51398d..3ab318255d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/CurrencyData.java @@ -30,11 +30,11 @@ public class CurrencyData { public static final class CurrencyFormatInfo { public final String currencyPattern; - public final char monetarySeparator; - public final char monetaryGroupingSeparator; + public final String monetarySeparator; + public final String monetaryGroupingSeparator; - public CurrencyFormatInfo(String currencyPattern, char monetarySeparator, - char monetaryGroupingSeparator) { + public CurrencyFormatInfo(String currencyPattern, String monetarySeparator, + String monetaryGroupingSeparator) { this.currencyPattern = currencyPattern; this.monetarySeparator = monetarySeparator; this.monetaryGroupingSeparator = monetaryGroupingSeparator; @@ -93,10 +93,12 @@ public class CurrencyData { temp = (CurrencyDisplayInfoProvider) clzz.newInstance(); } catch (Throwable t) { temp = new CurrencyDisplayInfoProvider() { + @Override public CurrencyDisplayInfo getInstance(ULocale locale, boolean withFallback) { return DefaultInfo.getWithFallback(withFallback); } + @Override public boolean hasData() { return false; } 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 ad01113d99..cdc719f98f 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 @@ -896,9 +896,9 @@ public class DecimalFormat extends NumberFormat { addPadding(result, fieldPosition, prefixLen, suffixLen); return result; } - + int precision = precision(false); - + // This is to fix rounding for scientific notation. See ticket:10542. // This code should go away when a permanent fix is done for ticket:9931. // @@ -957,7 +957,7 @@ public class DecimalFormat extends NumberFormat { } return toDigitList(number).getDouble(); } - + @Deprecated DigitList toDigitList(double number) { DigitList result = new DigitList(); @@ -1129,7 +1129,7 @@ public class DecimalFormat extends NumberFormat { digitList.set(number, precision(true)); // Issue 11808 if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) { - throw new ArithmeticException("Rounding necessary"); + throw new ArithmeticException("Rounding necessary"); } return subformat(number, result, fieldPosition, isNegative, true, parseAttr); } @@ -1164,7 +1164,7 @@ public class DecimalFormat extends NumberFormat { digitList.set(number, precision(true)); // For issue 11808. if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) { - throw new ArithmeticException("Rounding necessary"); + throw new ArithmeticException("Rounding necessary"); } return subformat(number.intValue(), result, fieldPosition, number.signum() < 0, true, parseAttr); @@ -1198,7 +1198,7 @@ public class DecimalFormat extends NumberFormat { !areSignificantDigitsUsed()); // For issue 11808. if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) { - throw new ArithmeticException("Rounding necessary"); + throw new ArithmeticException("Rounding necessary"); } return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0, false, parseAttr); @@ -1231,7 +1231,7 @@ public class DecimalFormat extends NumberFormat { !areSignificantDigitsUsed()); // For issue 11808. if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) { - throw new ArithmeticException("Rounding necessary"); + throw new ArithmeticException("Rounding necessary"); } return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0, false, false); @@ -1292,7 +1292,7 @@ public class DecimalFormat extends NumberFormat { // get the visible fractions and the number of fraction digits. return getFixedDecimal(number, digitList); } - + FixedDecimal getFixedDecimal(double number, DigitList dl) { int fractionalDigitsInDigitList = dl.count - dl.decimalAt; int v; @@ -1421,12 +1421,12 @@ public class DecimalFormat extends NumberFormat { FieldPosition fieldPosition, boolean isInteger, boolean parseAttr) { - char [] digits = symbols.getDigitsLocal(); + String[] digits = symbols.getDigitStrings(); - char grouping = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? - symbols.getGroupingSeparator(): symbols.getMonetaryGroupingSeparator(); - char decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? - symbols.getDecimalSeparator() : symbols.getMonetaryDecimalSeparator(); + String grouping = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? + symbols.getGroupingSeparatorString(): symbols.getMonetaryGroupingSeparatorString(); + String decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? + symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString(); boolean useSigDig = areSignificantDigitsUsed(); int maxIntDig = getMaximumIntegerDigits(); int minIntDig = getMinimumIntegerDigits(); @@ -1506,14 +1506,14 @@ public class DecimalFormat extends NumberFormat { fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) { fieldPosition.setEndIndex(result.length()); } - + // This handles the special case of formatting 0. For zero only, we count the // zero to the left of the decimal point as one signficant digit. Ordinarily we // do not count any leading 0's as significant. If the number we are formatting // is not zero, then either sigCount or digits.getCount() will be non-zero. if (sigCount == 0 && digitList.count == 0) { sigCount = 1; - } + } // Determine whether or not there are any printable fractional digits. If // we've used up the digits we know there aren't. @@ -1629,9 +1629,9 @@ public class DecimalFormat extends NumberFormat { private void subformatExponential(StringBuffer result, FieldPosition fieldPosition, boolean parseAttr) { - char [] digits = symbols.getDigitsLocal(); - char decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? - symbols.getDecimalSeparator() : symbols.getMonetaryDecimalSeparator(); + String[] digits = symbols.getDigitStringsLocal(); + String decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ? + symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString(); boolean useSigDig = areSignificantDigitsUsed(); int maxIntDig = getMaximumIntegerDigits(); int minIntDig = getMinimumIntegerDigits(); @@ -1777,7 +1777,7 @@ public class DecimalFormat extends NumberFormat { addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin, result.length()); } } - + // Record field information if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) { if (fieldPosition.getEndIndex() < 0) { @@ -1841,7 +1841,7 @@ public class DecimalFormat extends NumberFormat { if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { fieldPosition.setBeginIndex(result.length()); } - result.append(symbols.getMinusString()); + result.append(symbols.getMinusSignString()); if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { fieldPosition.setEndIndex(result.length()); } @@ -1855,7 +1855,7 @@ public class DecimalFormat extends NumberFormat { if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { fieldPosition.setBeginIndex(result.length()); } - result.append(symbols.getPlusString()); + result.append(symbols.getPlusSignString()); if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) { fieldPosition.setEndIndex(result.length()); } @@ -2353,7 +2353,7 @@ public class DecimalFormat extends NumberFormat { 0xFF0C, 0xFF0C, 0xFF0E, 0xFF0E, 0xFF61, 0xFF61).freeze(); - + static final UnicodeSet minusSigns = new UnicodeSet( 0x002D, 0x002D, @@ -2363,7 +2363,7 @@ public class DecimalFormat extends NumberFormat { 0x2796, 0x2796, 0xFE63, 0xFE63, 0xFF0D, 0xFF0D).freeze(); - + static final UnicodeSet plusSigns = new UnicodeSet( 0x002B, 0x002B, @@ -2373,7 +2373,7 @@ public class DecimalFormat extends NumberFormat { 0xFB29, 0xFB29, 0xFE62, 0xFE62, 0xFF0B, 0xFF0B).freeze(); - + // equivalent grouping and decimal support static final boolean skipExtendedSeparatorParsing = ICUConfig.get( "com.ibm.icu.text.DecimalFormat.SkipExtendedSeparatorParsing", "false") @@ -2460,25 +2460,22 @@ public class DecimalFormat extends NumberFormat { // DigitList, and adjust the exponent as needed. digits.decimalAt = digits.count = 0; - char [] digitSymbols = symbols.getDigitsLocal(); - char decimal = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ? - symbols.getDecimalSeparator() : symbols.getMonetaryDecimalSeparator(); - char grouping = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ? - symbols.getGroupingSeparator() : symbols.getMonetaryGroupingSeparator(); + String decimal = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ? + symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString(); + String grouping = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ? + symbols.getGroupingSeparatorString() : symbols.getMonetaryGroupingSeparatorString(); String exponentSep = symbols.getExponentSeparator(); boolean sawDecimal = false; boolean sawGrouping = false; - boolean sawExponent = false; boolean sawDigit = false; long exponent = 0; // Set to the exponent value, if any - int digit = 0; // strict parsing boolean strictParse = isParseStrict(); boolean strictFail = false; // did we exit with a strict parse failure? int lastGroup = -1; // where did we last see a grouping separator? - int digitStart = position; // where did the digit start? + int groupedDigitCount = 0; // tracking count of digits delimited by grouping separator int gs2 = groupingSize2 == 0 ? groupingSize : groupingSize2; UnicodeSet decimalEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY : @@ -2490,136 +2487,137 @@ public class DecimalFormat extends NumberFormat { // the maximum allowable digits is reached. int digitCount = 0; - int backup = -1; - int ch; - for (; position < text.length(); position += UTF16.getCharCount(ch)) { - ch = UTF16.charAt(text,position); + int backup = -1; // used for preserving the last confirmed position + int[] parsedDigit = {-1}; // allocates int[1] for parsing a single digit - - // We recognize all digit ranges, not only the Latin digit range - // '0'..'9'. We do so by using the UCharacter.digit() method, which - // converts a valid Unicode digit to the range 0..9. - // - // The character 'ch' may be a digit. If so, place its value from 0 to 9 - // in 'digit'. First try using the locale digit, which may or MAY NOT be a - // standard Unicode digit range. If this fails, try using the standard - // Unicode digit ranges by calling UCharacter.digit(). If this also fails, - // digit will have a value outside the range 0..9. - digit = ch - digitSymbols[0]; - if (digit < 0 || digit > 9) - digit = UCharacter.digit(ch, 10); - if (digit < 0 || digit > 9) { - for ( digit = 0 ; digit < 10 ; digit++) { - if ( ch == digitSymbols[digit] ) - break; - } - } - - - - if (digit == 0) { + while (position < text.length()) { + // Check if the sequence at the current position matches a decimal digit + int matchLen = matchesDigit(text, position, parsedDigit); + if (matchLen > 0) { + // matched a digit // Cancel out backup setting (see grouping handler below) - if (strictParse && backup != -1) { - // comma followed by digit, so group before comma is a secondary - // group. If there was a group separator before that, the group - // must == the secondary group length, else it can be <= the the - // secondary group length. - if ((lastGroup != -1 && countCodePoints(text, lastGroup, backup) - 1 != gs2) - || (lastGroup == -1 && countCodePoints(text, digitStart, position) - 1 > gs2)) { - strictFail = true; - break; + if (backup != -1) { + if (strictParse) { + // comma followed by digit, so group before comma is a secondary + // group. If there was a group separator before that, the group + // must == the secondary group length, else it can be <= the the + // secondary group length. + if ((lastGroup != -1 && groupedDigitCount != gs2) + || (lastGroup == -1 && groupedDigitCount > gs2)) { + strictFail = true; + break; + } } lastGroup = backup; + groupedDigitCount = 0; } - backup = -1; // Do this BEFORE continue statement below!!! - sawDigit = true; - // Handle leading zeros - if (digits.count == 0) { + groupedDigitCount++; + position += matchLen; + backup = -1; + sawDigit = true; + if (parsedDigit[0] == 0 && digits.count == 0) { + // Handle leading zeros if (!sawDecimal) { // Ignore leading zeros in integer part of number. continue; } - // If we have seen the decimal, but no significant digits yet, // then we account for leading zeros by decrementing the // digits.decimalAt into negative values. --digits.decimalAt; } else { ++digitCount; - digits.append((char) (digit + '0')); - } - } else if (digit > 0 && digit <= 9) // [sic] digit==0 handled above - { - if (strictParse) { - if (backup != -1) { - if ((lastGroup != -1 && countCodePoints(text, lastGroup, backup) - 1 != gs2) - || (lastGroup == -1 && countCodePoints(text, digitStart, position) - 1 > gs2)) { - strictFail = true; - break; - } - lastGroup = backup; - } + digits.append((char) (parsedDigit[0] + '0')); } + continue; + } - sawDigit = true; - ++digitCount; - digits.append((char) (digit + '0')); - - // Cancel out backup setting (see grouping handler below) - backup = -1; - } else if (ch == decimal) { + // Check if the sequence at the current position matches locale's decimal separator + int decimalStrLen = decimal.length(); + if (text.regionMatches(position, decimal, 0, decimalStrLen)) { + // matched a decimal separator if (strictParse) { if (backup != -1 || - (lastGroup != -1 && countCodePoints(text,lastGroup,position) != groupingSize + 1)) { + (lastGroup != -1 && groupedDigitCount != groupingSize)) { strictFail = true; break; } } + // If we're only parsing integers, or if we ALREADY saw the decimal, // then don't parse this one. if (isParseIntegerOnly() || sawDecimal) { break; } + digits.decimalAt = digitCount; // Not digits.count! sawDecimal = true; - } else if (isGroupingUsed() && ch == grouping) { - if (sawDecimal) { - break; - } - if (strictParse) { - if ((!sawDigit || backup != -1)) { - // leading group, or two group separators in a row - strictFail = true; + position += decimalStrLen; + continue; + } + + if (isGroupingUsed()) { + // Check if the sequence at the current position matches locale's grouping separator + int groupingStrLen = grouping.length(); + if (text.regionMatches(position, grouping, 0, groupingStrLen)) { + if (sawDecimal) { break; } + + if (strictParse) { + if ((!sawDigit || backup != -1)) { + // leading group, or two group separators in a row + strictFail = true; + break; + } + } + + // Ignore grouping characters, if we are using them, but require that + // they be followed by a digit. Otherwise we backup and reprocess + // them. + backup = position; + position += groupingStrLen; + sawGrouping = true; + continue; } - // Ignore grouping characters, if we are using them, but require that - // they be followed by a digit. Otherwise we backup and reprocess - // them. - backup = position; - sawGrouping = true; - } else if (!sawDecimal && decimalEquiv.contains(ch)) { + } + + // Check if the code point at the current position matches one of decimal/grouping equivalent group chars + int cp = text.codePointAt(position); + if (!sawDecimal && decimalEquiv.contains(cp)) { + // matched a decimal separator if (strictParse) { if (backup != -1 || - (lastGroup != -1 && countCodePoints(text,lastGroup,position) != groupingSize + 1)) { + (lastGroup != -1 && groupedDigitCount != groupingSize)) { strictFail = true; break; } } - // If we're only parsing integers, then don't parse this one. - if (isParseIntegerOnly()) + + // If we're only parsing integers, or if we ALREADY saw the decimal, + // then don't parse this one. + if (isParseIntegerOnly() || sawDecimal) { break; + } + digits.decimalAt = digitCount; // Not digits.count! // Once we see a decimal separator character, we only accept that // decimal separator character from then on. - decimal = (char) ch; + decimal = String.valueOf(Character.toChars(cp)); + sawDecimal = true; - } else if (isGroupingUsed() && !sawGrouping && groupEquiv.contains(ch)) { + position += Character.charCount(cp); + continue; + } + + if (isGroupingUsed() && !sawGrouping && groupEquiv.contains(cp)) { + // matched a grouping separator if (sawDecimal) { break; } + if (strictParse) { if ((!sawDigit || backup != -1)) { // leading group, or two group separators in a row @@ -2627,25 +2625,33 @@ public class DecimalFormat extends NumberFormat { break; } } + // Once we see a grouping character, we only accept that grouping // character from then on. - grouping = (char) ch; + grouping = String.valueOf(Character.toChars(cp)); // Ignore grouping characters, if we are using them, but require that // they be followed by a digit. Otherwise we backup and reprocess // them. backup = position; + position += Character.charCount(cp); sawGrouping = true; - } else if (!sawExponent && text.regionMatches(true, position, exponentSep, 0, exponentSep.length())) { - // Parse sign, if present + continue; + } + + // Check if the sequence at the current position matches locale's exponent separator + int exponentSepStrLen = exponentSep.length(); + if (text.regionMatches(true, position, exponentSep, 0, exponentSepStrLen)) { + // parse sign, if present boolean negExp = false; int pos = position + exponentSep.length(); if (pos < text.length()) { - ch = UTF16.charAt(text,pos); - if (ch == symbols.getPlusSign()) { - ++pos; - } else if (ch == symbols.getMinusSign()) { - ++pos; + String plusSign = symbols.getPlusSignString(); + String minusSign = symbols.getMinusSignString(); + if (text.regionMatches(pos, plusSign, 0, plusSign.length())) { + pos += plusSign.length(); + } else if (text.regionMatches(pos, minusSign, 0, minusSign.length())) { + pos += minusSign.length(); negExp = true; } } @@ -2653,16 +2659,10 @@ public class DecimalFormat extends NumberFormat { DigitList exponentDigits = new DigitList(); exponentDigits.count = 0; while (pos < text.length()) { - digit = UTF16.charAt(text,pos) - digitSymbols[0]; - if (digit < 0 || digit > 9) { - // Can't parse "[1E0]" when pattern is "0.###E0;[0.###E0]" - // Should update reassign the value of 'ch' in the code: digit - // = Character.digit(ch, 10); [Richard/GCL] - digit = UCharacter.digit(UTF16.charAt(text,pos), 10); - } - if (digit >= 0 && digit <= 9) { - exponentDigits.append((char) (digit + '0')); - pos += UTF16.getCharCount(UTF16.charAt(text,pos)); + int digitMatchLen = matchesDigit(text, pos, parsedDigit); + if (digitMatchLen > 0) { + exponentDigits.append((char) (parsedDigit[0] + '0')); + pos += digitMatchLen; } else { break; } @@ -2670,11 +2670,9 @@ public class DecimalFormat extends NumberFormat { if (exponentDigits.count > 0) { // defer strict parse until we know we have a bona-fide exponent - if (strictParse) { - if (backup != -1 || lastGroup != -1) { - strictFail = true; - break; - } + if (strictParse && sawGrouping) { + strictFail = true; + break; } // Quick overflow check for exponential part. Actual limit check @@ -2695,33 +2693,34 @@ public class DecimalFormat extends NumberFormat { } } position = pos; // Advance past the exponent - sawExponent = true; } break; // Whether we fail or succeed, we exit this loop - } else { - break; } + + // All other cases, stop parsing + break; } - if(digits.decimalAt == 0 && isDecimalPatternMatchRequired()) { - if(this.formatPattern.indexOf(decimal) != -1) { + if (digits.decimalAt == 0 && isDecimalPatternMatchRequired()) { + if (this.formatPattern.indexOf(decimal) != -1) { parsePosition.setIndex(oldStart); parsePosition.setErrorIndex(position); return false; } } - + if (backup != -1) position = backup; // If there was no decimal point we have an integer - if (!sawDecimal) + if (!sawDecimal) { digits.decimalAt = digitCount; // Not digits.count! + } // check for strict parse errors if (strictParse && !sawDecimal) { - if (lastGroup != -1 && countCodePoints(text,lastGroup,position) != groupingSize + 1) { + if (lastGroup != -1 && groupedDigitCount != groupingSize) { strictFail = true; } } @@ -2799,21 +2798,44 @@ public class DecimalFormat extends NumberFormat { return true; } - // Utility method used to count the number of codepoints - private int countCodePoints(String str,int start, int end) { - int count = 0; - int index = start; - while ( index < end ) { - count++; - index += UTF16.getCharCount(UTF16.charAt(str, index)); + /** + * Check if the substring at the specified position matches a decimal digit. + * If matched, this method sets the decimal value to decVal and + * returns matched length. + * + * @param str The input string + * @param start The start index + * @param decVal Receives decimal value + * @return Length of match, or 0 if the sequence at the position is not + * a decimal digit. + */ + private int matchesDigit(String str, int start, int[] decVal) { + String[] localeDigits = symbols.getDigitStringsLocal(); + + // Check if the sequence at the current position matches locale digits. + for (int i = 0; i < 10; i++) { + int digitStrLen = localeDigits[i].length(); + if (str.regionMatches(start, localeDigits[i], 0, digitStrLen)) { + decVal[0] = i; + return digitStrLen; + } } - return count; + + // If no locale digit match, then check if this is a Unicode digit + int cp = str.codePointAt(start); + decVal[0] = UCharacter.digit(cp, 10); + if (decVal[0] >= 0) { + return Character.charCount(cp); + } + + return 0; } + /** * Returns a set of characters equivalent to the given desimal separator used for * parsing number. This method may return an empty set. */ - private UnicodeSet getEquivalentDecimals(char decimal, boolean strictParse) { + private UnicodeSet getEquivalentDecimals(String decimal, boolean strictParse) { UnicodeSet equivSet = UnicodeSet.EMPTY; if (strictParse) { if (strictDotEquivalents.contains(decimal)) { @@ -2883,31 +2905,31 @@ public class DecimalFormat extends NumberFormat { /** * Remove bidi marks from affix */ - private static String trimMarksFromAffix(String affix) { - boolean hasBidiMark = false; - int idx = 0; - for (; idx < affix.length(); idx++) { - if (isBidiMark(affix.charAt(idx))) { - hasBidiMark = true; - break; - } - } - if (!hasBidiMark) { - return affix; - } + private static String trimMarksFromAffix(String affix) { + boolean hasBidiMark = false; + int idx = 0; + for (; idx < affix.length(); idx++) { + if (isBidiMark(affix.charAt(idx))) { + hasBidiMark = true; + break; + } + } + if (!hasBidiMark) { + return affix; + } - StringBuilder buf = new StringBuilder(); - buf.append(affix, 0, idx); - idx++; // skip the first Bidi mark - for (; idx < affix.length(); idx++) { - char c = affix.charAt(idx); - if (!isBidiMark(c)) { - buf.append(c); - } - } + StringBuilder buf = new StringBuilder(); + buf.append(affix, 0, idx); + idx++; // skip the first Bidi mark + for (; idx < affix.length(); idx++) { + char c = affix.charAt(idx); + if (!isBidiMark(c)) { + buf.append(c); + } + } - return buf.toString(); - } + return buf.toString(); + } /** * Return the length matched by the given affix, or -1 if none. Runs of white space in @@ -3083,6 +3105,8 @@ public class DecimalFormat extends NumberFormat { continue; } + String affix = null; + switch (c) { case CURRENCY_SIGN: // since the currency names in choice format is saved the same way as @@ -3132,15 +3156,27 @@ public class DecimalFormat extends NumberFormat { } continue; case PATTERN_PERCENT: - c = symbols.getPercent(); + affix = symbols.getPercentString(); break; case PATTERN_PER_MILLE: - c = symbols.getPerMill(); + affix = symbols.getPerMillString(); break; - case PATTERN_MINUS: - c = symbols.getMinusSign(); + case PATTERN_PLUS_SIGN: + affix = symbols.getPlusSignString(); + break; + case PATTERN_MINUS_SIGN: + affix = symbols.getMinusSignString(); + break; + default: + // fall through to affix != null test, which will fail break; } + + if (affix != null) { + pos = match(text, pos, affix); + continue; + } + pos = match(text, pos, c); if (PatternProps.isWhiteSpace(c)) { i = skipPatternWhiteSpace(affixPat, i); @@ -3186,6 +3222,9 @@ public class DecimalFormat extends NumberFormat { for (int i = 0; i < str.length() && pos >= 0;) { int ch = UTF16.charAt(str, i); i += UTF16.getCharCount(ch); + if (isBidiMark(ch)) { + continue; + } pos = match(text, pos, ch); if (PatternProps.isWhiteSpace(ch)) { i = skipPatternWhiteSpace(str, i); @@ -3850,12 +3889,12 @@ public class DecimalFormat extends NumberFormat { public boolean isDecimalSeparatorAlwaysShown() { return decimalSeparatorAlwaysShown; } - + /** * When decimal match is not required, the input does not have to * contain a decimal mark when there is a decimal mark specified in the - * pattern. - * @param value true if input must contain a match to decimal mark in pattern + * pattern. + * @param value true if input must contain a match to decimal mark in pattern * Default is false. * @stable ICU 54 */ @@ -4206,19 +4245,20 @@ public class DecimalFormat extends NumberFormat { } // Here is where FieldPosition could be set for CURRENCY PLURAL. buffer.append(s); - continue; + break; case PATTERN_PERCENT: - c = symbols.getPercent(); + buffer.append(symbols.getPercentString()); break; case PATTERN_PER_MILLE: - c = symbols.getPerMill(); + buffer.append(symbols.getPerMillString()); + break; + case PATTERN_MINUS_SIGN: + buffer.append(symbols.getMinusSignString()); + break; + default: + buffer.append(c); break; - case PATTERN_MINUS: - String minusString = symbols.getMinusString(); - buffer.append(minusString); - continue; } - buffer.append(c); } } @@ -4263,23 +4303,22 @@ public class DecimalFormat extends NumberFormat { int offset = affix.indexOf(symbols.getCurrencySymbol()); if (offset > -1) { formatAffix2Attribute(isPrefix, Field.CURRENCY, buf, offset, - symbols.getCurrencySymbol().length()); + symbols.getCurrencySymbol().length()); } - offset = affix.indexOf(symbols.getMinusString()); + offset = affix.indexOf(symbols.getMinusSignString()); if (offset > -1) { - formatAffix2Attribute(isPrefix, Field.SIGN, buf, offset, - symbols.getMinusString().length()); + formatAffix2Attribute(isPrefix, Field.SIGN, buf, offset, + symbols.getMinusSignString().length()); } - // TODO: Consider if Percent and Permille can be more than one character. - offset = affix.indexOf(symbols.getPercent()); + offset = affix.indexOf(symbols.getPercentString()); if (offset > -1) { - formatAffix2Attribute(isPrefix, Field.PERCENT, buf, offset, - 1); + formatAffix2Attribute(isPrefix, Field.PERCENT, buf, offset, + symbols.getPercentString().length()); } - offset = affix.indexOf(symbols.getPerMill()); + offset = affix.indexOf(symbols.getPerMillString()); if (offset > -1) { - formatAffix2Attribute(isPrefix, Field.PERMILLE, buf, offset, - 1); + formatAffix2Attribute(isPrefix, Field.PERMILLE, buf, offset, + symbols.getPerMillString().length()); } offset = pattern.indexOf("¤¤¤"); if (offset > -1) { @@ -4290,7 +4329,7 @@ public class DecimalFormat extends NumberFormat { // Look for SIGN, PERCENT, PERMILLE in the formatted affix. if (fieldPosition.getFieldAttribute() == NumberFormat.Field.SIGN) { - String sign = isNegative ? symbols.getMinusString() : symbols.getPlusString(); + String sign = isNegative ? symbols.getMinusSignString() : symbols.getPlusSignString(); int firstPos = affix.indexOf(sign); if (firstPos > -1) { int startPos = buf.length() + firstPos; @@ -4298,18 +4337,18 @@ public class DecimalFormat extends NumberFormat { fieldPosition.setEndIndex(startPos + sign.length()); } } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.PERCENT) { - int firstPos = affix.indexOf(symbols.getPercent()); + int firstPos = affix.indexOf(symbols.getPercentString()); if (firstPos > -1) { int startPos = buf.length() + firstPos; fieldPosition.setBeginIndex(startPos); - fieldPosition.setEndIndex(startPos + 1); + fieldPosition.setEndIndex(startPos + symbols.getPercentString().length()); } } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.PERMILLE) { - int firstPos = affix.indexOf(symbols.getPerMill()); + int firstPos = affix.indexOf(symbols.getPerMillString()); if (firstPos > -1) { int startPos = buf.length() + firstPos; fieldPosition.setBeginIndex(startPos); - fieldPosition.setEndIndex(startPos + 1); + fieldPosition.setEndIndex(startPos + symbols.getPerMillString().length()); } } else // If CurrencySymbol or InternationalCurrencySymbol is in the affix, check for currency symbol. @@ -4410,6 +4449,8 @@ public class DecimalFormat extends NumberFormat { /** * Appends an affix pattern to the given StringBuffer. Localize unquoted specials. + *

+ * Note: This implementation does not support new String localized symbols. */ private void appendAffixPattern(StringBuffer buffer, boolean isNegative, boolean isPrefix, boolean localized) { @@ -4462,7 +4503,7 @@ public class DecimalFormat extends NumberFormat { case PATTERN_PERCENT: ch = symbols.getPercent(); break; - case PATTERN_MINUS: + case PATTERN_MINUS_SIGN: ch = symbols.getMinusSign(); break; } @@ -4480,6 +4521,8 @@ public class DecimalFormat extends NumberFormat { /** * Does the real work of generating a pattern. + *

+ * Note: This implementation does not support new String localized symbols. */ private String toPattern(boolean localized) { StringBuffer result = new StringBuffer(); @@ -4619,7 +4662,7 @@ public class DecimalFormat extends NumberFormat { } if (part == 0) { if (negativeSuffix.equals(positiveSuffix) && - negativePrefix.equals(PATTERN_MINUS + positivePrefix)) { + negativePrefix.equals(PATTERN_MINUS_SIGN + positivePrefix)) { break; } else { result.append(localized ? symbols.getPatternSeparator() : PATTERN_SEPARATOR); @@ -4711,7 +4754,7 @@ public class DecimalFormat extends NumberFormat { String exponent = String.valueOf(PATTERN_EXPONENT); char plus = PATTERN_PLUS_SIGN; char padEscape = PATTERN_PAD_ESCAPE; - char minus = PATTERN_MINUS; // Bug 4212072 [Richard/GCL] + char minus = PATTERN_MINUS_SIGN; // Bug 4212072 [Richard/GCL] if (localized) { zeroDigit = symbols.getZeroDigit(); sigDigit = symbols.getSignificantDigit(); @@ -4973,7 +5016,7 @@ public class DecimalFormat extends NumberFormat { // Fall through to append(ch) } else if (ch == minus) { // Convert to non-localized pattern - ch = PATTERN_MINUS; + ch = PATTERN_MINUS_SIGN; // Fall through to append(ch) } else if (ch == padEscape) { if (padPos >= 0) { @@ -5167,7 +5210,7 @@ public class DecimalFormat extends NumberFormat { (negPrefixPattern.equals(posPrefixPattern) && negSuffixPattern.equals(posSuffixPattern))) { negSuffixPattern = posSuffixPattern; - negPrefixPattern = PATTERN_MINUS + posPrefixPattern; + negPrefixPattern = PATTERN_MINUS_SIGN + posPrefixPattern; } setLocale(null, null); // save the pattern @@ -5254,8 +5297,8 @@ public class DecimalFormat extends NumberFormat { /** * {@icu} Sets the minimum number of significant digits that will be displayed. If * min is less than one then it is set to one. If the maximum significant - * digits count is less than min, then it is set to min. - * This function also enables the use of significant digits by this formatter - + * digits count is less than min, then it is set to min. + * This function also enables the use of significant digits by this formatter - * {@link #areSignificantDigitsUsed()} will return true. * * @param min the fewest significant digits to be shown @@ -5276,7 +5319,7 @@ public class DecimalFormat extends NumberFormat { * {@icu} Sets the maximum number of significant digits that will be displayed. If * max is less than one then it is set to one. If the minimum significant * digits count is greater than max, then it is set to max. - * This function also enables the use of significant digits by this formatter - + * This function also enables the use of significant digits by this formatter - * {@link #areSignificantDigitsUsed()} will return true. * * @param max the most significant digits to be shown @@ -5353,12 +5396,12 @@ public class DecimalFormat extends NumberFormat { } } } - + /** * Sets the Currency Usage object used to display currency. * This takes effect immediately, if this format is a - * currency format. - * @param newUsage new currency context object to use. + * currency format. + * @param newUsage new currency context object to use. * @stable ICU 54 */ public void setCurrencyUsage(CurrencyUsage newUsage) { @@ -5384,7 +5427,7 @@ public class DecimalFormat extends NumberFormat { public CurrencyUsage getCurrencyUsage() { return currencyUsage; } - + /** * Returns the currency in effect for this formatter. Subclasses should override this * method as needed. Unlike getCurrency(), this method should never return null. @@ -5456,9 +5499,9 @@ public class DecimalFormat extends NumberFormat { public boolean isParseBigDecimal() { return parseBigDecimal; } - + /** - * Set the maximum number of exponent digits when parsing a number. + * Set the maximum number of exponent digits when parsing a number. * If the limit is set too high, an OutOfMemoryException may be triggered. * The default value is 1000. * @param newValue the new limit @@ -5469,7 +5512,7 @@ public class DecimalFormat extends NumberFormat { PARSE_MAX_EXPONENT = newValue; } } - + /** * Get the current maximum number of exponent digits when parsing a * number. @@ -5856,7 +5899,7 @@ public class DecimalFormat extends NumberFormat { * @since ICU 54 */ private CurrencyUsage currencyUsage = CurrencyUsage.STANDARD; - + // ---------------------------------------------------------------------- static final int currentSerialVersion = 4; @@ -5878,7 +5921,7 @@ public class DecimalFormat extends NumberFormat { *

  • 3: ICU 2.2. Adds currency object. * *
  • 4: ICU 54. Adds currency usage(standard vs cash) - * + * * * * @serial @@ -5958,15 +6001,12 @@ public class DecimalFormat extends NumberFormat { static final char PATTERN_SIGNIFICANT_DIGIT = '@'; static final char PATTERN_EXPONENT = 'E'; static final char PATTERN_PLUS_SIGN = '+'; + static final char PATTERN_MINUS_SIGN = '-'; // Affix private static final char PATTERN_PER_MILLE = '\u2030'; private static final char PATTERN_PERCENT = '%'; static final char PATTERN_PAD_ESCAPE = '*'; - /** - * Bug 4212072 To meet the need of expandAffix(String, StirngBuffer) [Richard/GCL] - */ - private static final char PATTERN_MINUS = '-'; // Other private static final char PATTERN_SEPARATOR = ';'; @@ -6176,7 +6216,7 @@ public class DecimalFormat extends NumberFormat { * based on rounding mode and width of fractional digits. Whenever setting affecting * rounding mode, rounding increment and maximum width of fractional digits, then * this method must be called. - * + * * roundingIncrementICU is the field storing the custom rounding increment value, * while actual rounding increment could be larger. */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java index 17e1bec30c..6b970a3e8c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java @@ -165,64 +165,128 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * Returns the character used for zero. Different for Arabic, etc. * @return the character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getDigitStrings()} instead. */ public char getZeroDigit() { - if ( digits != null ) { - return digits[0]; - } else { - return zeroDigit; - } + return zeroDigit; } + /** * Returns the array of characters used as digits, in order from 0 through 9 * @return The array * @stable ICU 4.6 + * @see #getDigitStrings() + * @discouraged ICU 58 use {@link #getDigitStrings()} instead. */ public char[] getDigits() { - if ( digits != null ) { - return digits.clone(); - } else { - char [] digitArray = new char[10]; - for ( int i = 0 ; i < 10 ; i++ ) { - digitArray[i] = (char) (zeroDigit + i); - } - return digitArray; - } - } - - /** - * Returns the array of characters used as digits, in order from 0 through 9 - * Package private method - don't need to defensively copy. - * @return The array - */ - char[] getDigitsLocal() { - if ( digits != null ) { - return digits; - } else { - char [] digitArray = new char[10]; - for ( int i = 0 ; i < 10 ; i++ ) { - digitArray[i] = (char) (zeroDigit + i); - } - return digitArray; - } + return digits.clone(); } /** * Sets the character used for zero. + *

    + * Note:

    When the specified zeroDigit is a Unicode decimal digit character + * (category:Nd) and the number value is 0, then this method propagate digit 1 to + * digit 9 by incrementing code point one by one. + * * @param zeroDigit the zero character. * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #setDigitStrings(String[])} instead. */ public void setZeroDigit(char zeroDigit) { - if ( digits != null ) { - digits = digits.clone(); // Do not change cached digits. - this.digits[0] = zeroDigit; - if (Character.digit(zeroDigit,10) == 0) { - for ( int i = 1 ; i < 10 ; i++ ) { - this.digits[i] = (char)(zeroDigit+i); - } + this.zeroDigit = zeroDigit; + + // digitStrings or digits might be referencing a cached copy for + // optimization purpose, so creating a copy before making a modification + digitStrings = digitStrings.clone(); + digits = digits.clone(); + + // Make digitStrings field and digits field in sync + digitStrings[0] = String.valueOf(zeroDigit); + digits[0] = zeroDigit; + + // Propagate digit 1 - 9 only when the input zeroDigit is a + // Unicode number and its integer value is 0. + + if (Character.digit(zeroDigit, 10) == 0) { + for (int i = 1; i < 10; i++) { + char d = (char)(zeroDigit + i); + digitStrings[i] = String.valueOf(d); + digits[i] = d; } + } + } + + /** + * {@icu} Returns the array of strings used as digits, in order from 0 through 9 + * @return The array of ten digit strings + * @see #setDigitStrings(String[]) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String[] getDigitStrings() { + return digitStrings.clone(); + } + + /** + * Returns the array of strings used as digits, in order from 0 through 9 + * Package private method - doesn't create a defensively copy. + * @return the array of digit strings + */ + String[] getDigitStringsLocal() { + return digitStrings; + } + + /** + * {@icu} Sets the array of strings used as digits, in order from 0 through 9 + *

    + * Note: + *

    + * When the input array of digit strings contains any strings + * represented by multiple Java chars, then {@link #getDigits()} will return + * the default digits ('0' - '9') and {@link #getZeroDigit()} will return the + * default zero digit ('0'). + * + * @param digitStrings The array of digit strings. The length of the array must be exactly 10. + * @throws NullPointerException if the digitStrings is null. + * @throws IllegalArgumentException if the length of the array is not 10. + * @see #getDigitStrings() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setDigitStrings(String[] digitStrings) { + if (digitStrings == null) { + throw new NullPointerException("The input digit string array is null"); + } + if (digitStrings.length != 10) { + throw new IllegalArgumentException("Number of digit strings is not 10"); + } + + // Scan input array and create char[] representation if possible + String[] tmpDigitStrings = new String[10]; + char[] tmpDigits = new char[10]; + for (int i = 0; i < 10; i++) { + if (digitStrings[i] == null) { + throw new IllegalArgumentException("The input digit string array contains a null element"); + } + tmpDigitStrings[i] = digitStrings[i]; + if (tmpDigits != null && digitStrings[i].length() == 1) { + tmpDigits[i] = digitStrings[i].charAt(0); + } else { + // contains digit string with multiple UTF-16 code units + tmpDigits = null; + } + } + + this.digitStrings = tmpDigitStrings; + + if (tmpDigits == null) { + // fallback to the default digit chars + this.zeroDigit = DEF_DIGIT_CHARS_ARRAY[0]; + this.digits = DEF_DIGIT_CHARS_ARRAY; } else { - this.zeroDigit = zeroDigit; + this.zeroDigit = tmpDigits[0]; + this.digits = tmpDigits; } } @@ -245,27 +309,68 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { } /** - * Returns the character used for thousands separator. Different for French, etc. + * Returns the character used for grouping separator. Different for French, etc. * @return the thousands character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getGroupingSeparatorString()} instead. */ public char getGroupingSeparator() { return groupingSeparator; } /** - * Sets the character used for thousands separator. Different for French, etc. + * Sets the character used for grouping separator. Different for French, etc. * @param groupingSeparator the thousands character * @stable ICU 2.0 + * @see #setGroupingSeparatorString(String) */ public void setGroupingSeparator(char groupingSeparator) { this.groupingSeparator = groupingSeparator; + this.groupingSeparatorString = String.valueOf(groupingSeparator); + } + + /** + * {@icu} Returns the string used for grouping separator. Different for French, etc. + * @return the grouping separator string + * @see #setGroupingSeparatorString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getGroupingSeparatorString() { + return groupingSeparatorString; + } + + /** + * {@icu} Sets the string used for grouping separator. + *

    + * Note: When the input grouping separator String is represented + * by multiple Java chars, then {@link #getGroupingSeparator()} will + * return the default grouping separator character (','). + * + * @param groupingSeparatorString the grouping separator string + * @throws NullPointerException if groupingSeparatorString is null. + * @see #getGroupingSeparatorString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setGroupingSeparatorString(String groupingSeparatorString) { + if (groupingSeparatorString == null) { + throw new NullPointerException("The input grouping separator is null"); + } + this.groupingSeparatorString = groupingSeparatorString; + if (groupingSeparatorString.length() == 1) { + this.groupingSeparator = groupingSeparatorString.charAt(0); + } else { + // Use the default grouping separator character as fallback + this.groupingSeparator = DEF_GROUPING_SEPARATOR; + } } /** * Returns the character used for decimal sign. Different for French, etc. * @return the decimal character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getDecimalSeparatorString()} instead. */ public char getDecimalSeparator() { return decimalSeparator; @@ -278,12 +383,51 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ public void setDecimalSeparator(char decimalSeparator) { this.decimalSeparator = decimalSeparator; + this.decimalSeparatorString = String.valueOf(decimalSeparator); + } + + /** + * {@icu} Returns the string used for decimal sign. + * @return the decimal sign string + * @see #setDecimalSeparatorString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getDecimalSeparatorString() { + return decimalSeparatorString; + } + + /** + * {@icu} Sets the string used for decimal sign. + *

    + * Note: When the input decimal separator String is represented + * by multiple Java chars, then {@link #getDecimalSeparator()} will + * return the default decimal separator character ('.'). + * + * @param decimalSeparatorString the decimal sign string + * @throws NullPointerException if decimalSeparatorString is null. + * @see #getDecimalSeparatorString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setDecimalSeparatorString(String decimalSeparatorString) { + if (decimalSeparatorString == null) { + throw new NullPointerException("The input decimal separator is null"); + } + this.decimalSeparatorString = decimalSeparatorString; + if (decimalSeparatorString.length() == 1) { + this.decimalSeparator = decimalSeparatorString.charAt(0); + } else { + // Use the default decimal separator character as fallback + this.decimalSeparator = DEF_DECIMAL_SEPARATOR; + } } /** * Returns the character used for mille percent sign. Different for Arabic, etc. * @return the mille percent character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getPerMillString()} instead. */ public char getPerMill() { return perMill; @@ -296,12 +440,51 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ public void setPerMill(char perMill) { this.perMill = perMill; + this.perMillString = String.valueOf(perMill); + } + + /** + * {@icu} Returns the string used for permille sign. + * @return the permille string + * @see #setPerMillString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getPerMillString() { + return perMillString; + } + + /** + * {@icu} Sets the string used for permille sign. + *

    + * Note: When the input permille String is represented + * by multiple Java chars, then {@link #getPerMill()} will + * return the default permille character ('‰'). + * + * @param perMillString the permille string + * @throws NullPointerException if perMillString is null. + * @see #getPerMillString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setPerMillString(String perMillString) { + if (perMillString == null) { + throw new NullPointerException("The input permille string is null"); + } + this.perMillString = perMillString; + if (perMillString.length() == 1) { + this.perMill = perMillString.charAt(0); + } else { + // Use the default permille character as fallback + this.perMill = DEF_PERMILL; + } } /** * Returns the character used for percent sign. Different for Arabic, etc. * @return the percent character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getPercentString()} instead. */ public char getPercent() { return percent; @@ -314,6 +497,44 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ public void setPercent(char percent) { this.percent = percent; + this.percentString = String.valueOf(percent); + } + + /** + * {@icu} Returns the string used for percent sign. + * @return the percent string + * @see #setPercentString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getPercentString() { + return percentString; + } + + /** + * {@icu} Sets the string used for percent sign. + *

    + * Note: When the input grouping separator String is represented + * by multiple Java chars, then {@link #getPercent()} will + * return the default percent sign character ('%'). + * + * @param percentString the percent string + * @throws NullPointerException if percentString is null. + * @see #getPercentString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setPercentString(String percentString) { + if (percentString == null) { + throw new NullPointerException("The input percent sign is null"); + } + this.percentString = percentString; + if (percentString.length() == 1) { + this.percent = percentString.charAt(0); + } else { + // Use default percent character as fallback + this.percent = DEF_PERCENT; + } } /** @@ -403,22 +624,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * minusSign to the positive format. * @return the minus sign character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getMinusSignString()} instead. */ public char getMinusSign() { return minusSign; } - /** - * Returns the string used to represent minus sign. - * @return the minus sign string - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public String getMinusString() { - return minusString; - } - /** * Sets the character used to represent minus sign. If no explicit * negative format is specified, one is formed by prefixing @@ -428,9 +639,109 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ public void setMinusSign(char minusSign) { this.minusSign = minusSign; - // Also updates minusString - char[] minusArray = { minusSign }; - minusString = new String(minusArray); + this.minusString = String.valueOf(minusSign); + } + + /** + * {@icu} Returns the string used to represent minus sign. + * @return the minus sign string + * @see #setMinusSignString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getMinusSignString() { + return minusString; + } + + /** + * {@icu} Sets the string used to represent minus sign. + *

    + * Note: When the input minus sign String is represented + * by multiple Java chars, then {@link #getMinusSign()} will + * return the default minus sign character ('-'). + * + * @param minusSignString the minus sign string + * @throws NullPointerException if minusSignString is null. + * @see #getGroupingSeparatorString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setMinusSignString(String minusSignString) { + if (minusSignString == null) { + throw new NullPointerException("The input minus sign is null"); + } + this.minusString = minusSignString; + if (minusSignString.length() == 1) { + this.minusSign = minusSignString.charAt(0); + } else { + // Use the default minus sign as fallback + this.minusSign = DEF_MINUS_SIGN; + } + } + + /** + * {@icu} Returns the localized plus sign. + * @return the plus sign, used in localized patterns and formatted + * strings + * @see #setPlusSign + * @see #setMinusSign + * @see #getMinusSign + * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getPlusSignString()} instead. + */ + public char getPlusSign() { + return plusSign; + } + + /** + * {@icu} Sets the localized plus sign. + * @param plus the plus sign, used in localized patterns and formatted + * strings + * @see #getPlusSign + * @see #setMinusSign + * @see #getMinusSign + * @stable ICU 2.0 + */ + public void setPlusSign(char plus) { + this.plusSign = plus; + this.plusString = String.valueOf(plus); + } + + /** + * {@icu} Returns the string used to represent plus sign. + * @return the plus sign string + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getPlusSignString() { + return plusString; + } + + /** + * {@icu} Sets the localized plus sign string. + *

    + * Note: When the input plus sign String is represented + * by multiple Java chars, then {@link #getPlusSign()} will + * return the default plus sign character ('+'). + * + * @param plusSignString the plus sign string, used in localized patterns and formatted + * strings + * @throws NullPointerException if plusSignString is null. + * @see #getPlusSignString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setPlusSignString(String plusSignString) { + if (plusSignString == null) { + throw new NullPointerException("The input plus sign is null"); + } + this.plusString = plusSignString; + if (plusSignString.length() == 1) { + this.plusSign = plusSignString.charAt(0); + } else { + // Use the default plus sign as fallback + this.plusSign = DEF_PLUS_SIGN; + } } /** @@ -509,46 +820,125 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * Returns the monetary decimal separator. * @return the monetary decimal separator character * @stable ICU 2.0 + * @discouraged ICU 58 use {@link #getMonetaryDecimalSeparatorString()} instead. */ public char getMonetaryDecimalSeparator() { return monetarySeparator; } - /** - * {@icu} Returns the monetary grouping separator. - * @return the monetary grouping separator character - * @stable ICU 3.6 - */ - public char getMonetaryGroupingSeparator() { - return monetaryGroupingSeparator; - } - - /** - * Internal API for NumberFormat - * @return String currency pattern string - */ - String getCurrencyPattern() { - return currencyPattern; - } - /** * Sets the monetary decimal separator. * @param sep the monetary decimal separator character * @stable ICU 2.0 */ public void setMonetaryDecimalSeparator(char sep) { - monetarySeparator = sep; + this.monetarySeparator = sep; + this.monetarySeparatorString = String.valueOf(sep); } /** - * Sets the monetary decimal separator. - * @param sep the monetary decimal separator character + * {@icu} Returns the monetary decimal separator string. + * @return the monetary decimal separator string + * @see #setMonetaryDecimalSeparatorString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getMonetaryDecimalSeparatorString() { + return monetarySeparatorString; + } + + /** + * {@icu} Sets the monetary decimal separator string. + *

    + * Note: When the input monetary decimal separator String is represented + * by multiple Java chars, then {@link #getMonetaryDecimalSeparatorString()} will + * return the default monetary decimal separator character ('.'). + * + * @param sep the monetary decimal separator string + * @throws NullPointerException if sep is null. + * @see #getMonetaryDecimalSeparatorString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setMonetaryDecimalSeparatorString(String sep) { + if (sep == null) { + throw new NullPointerException("The input monetary decimal separator is null"); + } + this.monetarySeparatorString = sep; + if (sep.length() == 1) { + this.monetarySeparator = sep.charAt(0); + } else { + // Use default decimap separator character as fallbacl + this.monetarySeparator = DEF_DECIMAL_SEPARATOR; + } + } + + /** + * {@icu} Returns the monetary grouping separator. + * @return the monetary grouping separator character + * @stable ICU 3.6 + * @discouraged ICU 58 use {@link #getMonetaryGroupingSeparatorString()} instead. + */ + public char getMonetaryGroupingSeparator() { + return monetaryGroupingSeparator; + } + + /** + * {@icu} Sets the monetary grouping separator. + * @param sep the monetary grouping separator character * @stable ICU 3.6 */ public void setMonetaryGroupingSeparator(char sep) { - monetaryGroupingSeparator = sep; + this.monetaryGroupingSeparator = sep; + this.monetaryGroupingSeparatorString = String.valueOf(sep); } - + + /** + * {@icu} Returns the monetary grouping separator. + * @return the monetary grouping separator string + * @see #setMonetaryGroupingSeparatorString(String) + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public String getMonetaryGroupingSeparatorString() { + return monetaryGroupingSeparatorString; + } + + /** + * {@icu} Sets the monetary grouping separator string. + *

    + * Note: When the input grouping separator String is represented + * by multiple Java chars, then {@link #getMonetaryGroupingSeparator()} will + * return the default monetary grouping separator character (','). + * + * @param sep the monetary grouping separator string + * @throws NullPointerException if sep is null. + * @see #getMonetaryGroupingSeparatorString() + * @draft ICU 58 + * @provisional This API might change or be removed in a future release. + */ + public void setMonetaryGroupingSeparatorString(String sep) { + if (sep == null) { + throw new NullPointerException("The input monetary grouping separator is null"); + } + this.monetaryGroupingSeparatorString = sep; + if (sep.length() == 1) { + this.monetaryGroupingSeparator = sep.charAt(0); + } else { + // Use default grouping separator character as fallback + this.monetaryGroupingSeparator = DEF_GROUPING_SEPARATOR; + } + } + + /** + } + * Internal API for NumberFormat + * @return String currency pattern string + */ + String getCurrencyPattern() { + return currencyPattern; + } + /** * Returns the multiplication sign * @stable ICU 54 @@ -556,7 +946,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { public String getExponentMultiplicationSign() { return exponentMultiplicationSign; } - + /** * Sets the multiplication sign * @stable ICU 54 @@ -589,46 +979,6 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { exponentSeparator = exp; } - /** - * {@icu} Returns the localized plus sign. - * @return the plus sign, used in localized patterns and formatted - * strings - * @see #setPlusSign - * @see #setMinusSign - * @see #getMinusSign - * @stable ICU 2.0 - */ - public char getPlusSign() { - return plusSign; - } - - /** - * Returns the string used to represent plus sign. - * @return the plus sign string - * @internal - * @deprecated This API is ICU internal only. - */ - @Deprecated - public String getPlusString() { - return plusString; - } - - /** - * {@icu} Sets the localized plus sign. - * @param plus the plus sign, used in localized patterns and formatted - * strings - * @see #getPlusSign - * @see #setMinusSign - * @see #getMinusSign - * @stable ICU 2.0 - */ - public void setPlusSign(char plus) { - plusSign = plus; - // Also updates plusString - char[] plusArray = { plusSign }; - plusString = new String(plusArray); - } - /** * {@icu} Returns the character used to pad numbers out to a specified width. This is * not the pad character itself; rather, it is the special pattern character @@ -766,9 +1116,10 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * {@inheritDoc} * @stable ICU 2.0 */ + @Override public Object clone() { try { - return (DecimalFormatSymbols) super.clone(); + return super.clone(); // other fields are bit-copied } catch (CloneNotSupportedException e) { ///CLOVER:OFF @@ -781,6 +1132,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * {@inheritDoc} * @stable ICU 2.0 */ + @Override public boolean equals(Object obj) { if (!(obj instanceof DecimalFormatSymbols)) { return false; @@ -797,7 +1149,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { return false; } } - + if ( other.digits == null ) { for (int i = 0 ; i < 10 ; i++) { if (digits[i] != other.zeroDigit + i) { @@ -834,6 +1186,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * {@inheritDoc} * @stable ICU 2.0 */ + @Override public int hashCode() { int result = digits[0]; result = result * 37 + groupingSeparator; @@ -841,13 +1194,6 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { return result; } - /** - * Check for bidi marks: LRM, RLM, ALM - */ - private static boolean isBidiMark(char c) { - return (c=='\u200E' || c=='\u200F' || c=='\u061C'); - } - /** * List of field names to be loaded from the data files. * The indices of each name into the array correspond to the position of that item in the @@ -869,18 +1215,37 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { "superscriptingExponent" }; + /* + * Default digits + */ + private static final String[] DEF_DIGIT_STRINGS_ARRAY = + {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + + private static final char[] DEF_DIGIT_CHARS_ARRAY = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + /* + * Default symbol characters, used for fallbacks. + */ + private static final char DEF_DECIMAL_SEPARATOR = '.'; + private static final char DEF_GROUPING_SEPARATOR = ','; + private static final char DEF_PERCENT = '%'; + private static final char DEF_MINUS_SIGN = '-'; + private static final char DEF_PLUS_SIGN = '+'; + private static final char DEF_PERMILL = '\u2030'; + /** * List of default values for the symbols. */ private static final String[] SYMBOL_DEFAULTS = new String[] { - ".", // decimal - ",", // group + String.valueOf(DEF_DECIMAL_SEPARATOR), // decimal + String.valueOf(DEF_GROUPING_SEPARATOR), // group ";", // list - "%", // percentSign - "-", // minusSign - "+", // plusSign + String.valueOf(DEF_PERCENT), // percentSign + String.valueOf(DEF_MINUS_SIGN), // minusSign + String.valueOf(DEF_PLUS_SIGN), // plusSign "E", // exponential - "\u2030", // perMille + String.valueOf(DEF_PERMILL), // perMille "\u221e", // infinity "NaN", // NaN null, // currency decimal @@ -934,25 +1299,27 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { this.ulocale = locale; CacheData data = cachedLocaleData.getInstance(locale, null /* unused */); setLocale(data.validLocale, data.validLocale); - digits = data.digits; + setDigitStrings(data.digits); String[] numberElements = data.numberElements; // Copy data from the numberElements map into instance fields - decimalSeparator = numberElements[0].charAt(0); - groupingSeparator = numberElements[1].charAt(0); + setDecimalSeparatorString(numberElements[0]); + setGroupingSeparatorString(numberElements[1]); + + // See CLDR #9781 + // assert numberElements[2].length() == 1; patternSeparator = numberElements[2].charAt(0); - percent = numberElements[3].charAt(0); - minusString = numberElements[4]; - minusSign = (minusString.length() > 1 && isBidiMark(minusString.charAt(0)))? minusString.charAt(1): minusString.charAt(0); - plusString = numberElements[5]; - plusSign = (plusString.length() > 1 && isBidiMark(plusString.charAt(0)))? plusString.charAt(1): plusString.charAt(0); - exponentSeparator = numberElements[6]; - perMill = numberElements[7].charAt(0); - infinity = numberElements[8]; - NaN = numberElements[9]; - monetarySeparator = numberElements[10].charAt(0); - monetaryGroupingSeparator = numberElements[11].charAt(0); - exponentMultiplicationSign = numberElements[12]; + + setPercentString(numberElements[3]); + setMinusSignString(numberElements[4]); + setPlusSignString(numberElements[5]); + setExponentSeparator(numberElements[6]); + setPerMillString(numberElements[7]); + setInfinity(numberElements[8]); + setNaN(numberElements[9]); + setMonetaryDecimalSeparatorString(numberElements[10]); + setMonetaryGroupingSeparatorString(numberElements[11]); + setExponentMultiplicationSign(numberElements[12]); digit = DecimalFormat.PATTERN_DIGIT; // Localized pattern character no longer in CLDR padEscape = DecimalFormat.PATTERN_PAD_ESCAPE; @@ -971,8 +1338,8 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { CurrencyFormatInfo fmtInfo = info.getFormatInfo(intlCurrencySymbol); if (fmtInfo != null) { currencyPattern = fmtInfo.currencyPattern; - monetarySeparator = fmtInfo.monetarySeparator; - monetaryGroupingSeparator = fmtInfo.monetaryGroupingSeparator; + setMonetaryDecimalSeparatorString(fmtInfo.monetarySeparator); + setMonetaryGroupingSeparatorString(fmtInfo.monetaryGroupingSeparator); } } else { intlCurrencySymbol = "XXX"; @@ -988,33 +1355,22 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { String nsName; // Attempt to set the decimal digits based on the numbering system for the requested locale. NumberingSystem ns = NumberingSystem.getInstance(locale); - char[] digits = new char[10]; + String[] digits = new String[10]; if (ns != null && ns.getRadix() == 10 && !ns.isAlgorithmic() && NumberingSystem.isValidDigitString(ns.getDescription())) { String digitString = ns.getDescription(); - digits[0] = digitString.charAt(0); - digits[1] = digitString.charAt(1); - digits[2] = digitString.charAt(2); - digits[3] = digitString.charAt(3); - digits[4] = digitString.charAt(4); - digits[5] = digitString.charAt(5); - digits[6] = digitString.charAt(6); - digits[7] = digitString.charAt(7); - digits[8] = digitString.charAt(8); - digits[9] = digitString.charAt(9); + + for (int i = 0, offset = 0; i < 10; i++) { + int cp = digitString.codePointAt(offset); + int nextOffset = offset + Character.charCount(cp); + digits[i] = digitString.substring(offset, nextOffset); + offset = nextOffset; + } nsName = ns.getName(); } else { - digits[0] = DecimalFormat.PATTERN_ZERO_DIGIT; - digits[1] = DecimalFormat.PATTERN_ONE_DIGIT; - digits[2] = DecimalFormat.PATTERN_TWO_DIGIT; - digits[3] = DecimalFormat.PATTERN_THREE_DIGIT; - digits[4] = DecimalFormat.PATTERN_FOUR_DIGIT; - digits[5] = DecimalFormat.PATTERN_FIVE_DIGIT; - digits[6] = DecimalFormat.PATTERN_SIX_DIGIT; - digits[7] = DecimalFormat.PATTERN_SEVEN_DIGIT; - digits[8] = DecimalFormat.PATTERN_EIGHT_DIGIT; - digits[9] = DecimalFormat.PATTERN_NINE_DIGIT; - nsName = "latn"; // Default numbering system + // Default numbering system + digits = DEF_DIGIT_STRINGS_ARRAY; + nsName = "latn"; } // Open the resource bundle and get the locale IDs. @@ -1047,6 +1403,13 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { rb.getAllItemsWithFallback(NUMBER_ELEMENTS + "/" + LATIN_NUMBERING_SYSTEM + "/" + SYMBOLS, sink); } + // Fill in any remaining missing values + for (int i = 0; i < SYMBOL_KEYS.length; i++) { + if (numberElements[i] == null) { + numberElements[i] = SYMBOL_DEFAULTS[i]; + } + } + // If monetary decimal or grouping were not explicitly set, then set them to be the same as // their non-monetary counterparts. if (numberElements[10] == null) { @@ -1056,13 +1419,6 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { numberElements[11] = numberElements[1]; } - // Fill in any remaining missing values - for (int i = 0; i < SYMBOL_KEYS.length; i++) { - if (numberElements[i] == null) { - numberElements[i] = SYMBOL_DEFAULTS[i]; - } - } - return new CacheData(validLocale, digits, numberElements); } @@ -1133,12 +1489,10 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { if (serialVersionOnStream < 7) { // Set minusString,plusString from minusSign,plusSign if (minusString == null) { - char[] minusArray = { minusSign }; - minusString = new String(minusArray); + minusString = String.valueOf(minusSign); } if (plusString == null) { - char[] plusArray = { plusSign }; - plusString = new String(plusArray); + plusString = String.valueOf(plusSign); } } if (serialVersionOnStream < 8) { @@ -1146,6 +1500,48 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { exponentMultiplicationSign = "\u00D7"; } } + if (serialVersionOnStream < 9) { + // String version of digits + if (digitStrings == null) { + digitStrings = new String[10]; + if (digits != null && digits.length == 10) { + for (int i = 0; i < 10; i++) { + digitStrings[i] = String.valueOf(digits[i]); + } + } else { + char digit = zeroDigit; + if (digits == null) { + digits = new char[10]; + } + for (int i = 0; i < 10; i++) { + digits[i] = digit; + digitStrings[i] = String.valueOf(digit); + digit++; + } + } + } + + // String version of symbols + if (decimalSeparatorString == null) { + decimalSeparatorString = String.valueOf(decimalSeparator); + } + if (groupingSeparatorString == null) { + groupingSeparatorString = String.valueOf(groupingSeparator); + } + if (percentString == null) { + percentString = String.valueOf(percentString); + } + if (perMillString == null) { + perMillString = String.valueOf(perMill); + } + if (monetarySeparatorString == null) { + monetarySeparatorString = String.valueOf(monetarySeparator); + } + if (monetaryGroupingSeparatorString == null) { + monetaryGroupingSeparatorString = String.valueOf(monetaryGroupingSeparatorString); + } + } + serialVersionOnStream = currentSerialVersion; // recreate @@ -1160,13 +1556,18 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * @see #getZeroDigit */ private char zeroDigit; - + /** * Array of characters used for the digits 0-9 in order. - * - */ + */ private char digits[]; + /** + * Array of Strings used for the digits 0-9 in order. + * @serial + */ + private String digitStrings[]; + /** * Character used for thousands separator. * @@ -1175,6 +1576,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private char groupingSeparator; + /** + * String used for thousands separator. + * @serial + */ + private String groupingSeparatorString; + /** * Character used for decimal sign. * @@ -1183,6 +1590,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private char decimalSeparator; + /** + * String used for decimal sign. + * @serial + */ + private String decimalSeparatorString; + /** * Character used for mille percent sign. * @@ -1191,6 +1604,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private char perMill; + /** + * String used for mille percent sign. + * @serial + */ + private String perMillString; + /** * Character used for percent sign. * @serial @@ -1198,6 +1617,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private char percent; + /** + * String used for percent sign. + * @serial + */ + private String percentString; + /** * Character used for a digit in a pattern. * @@ -1244,6 +1669,27 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private char minusSign; + /** + * String versions of minus sign. + * @serial + * @since ICU 52 + */ + private String minusString; + + /** + * The character used to indicate a plus sign. + * @serial + * @since AlphaWorks + */ + private char plusSign; + + /** + * String versions of plus sign. + * @serial + * @since ICU 52 + */ + private String plusString; + /** * String denoting the local currency, e.g. "$". * @serial @@ -1259,19 +1705,31 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { private String intlCurrencySymbol; /** - * The decimal separator used when formatting currency values. + * The decimal separator character used when formatting currency values. * @serial * @see #getMonetaryDecimalSeparator */ private char monetarySeparator; // Field new in JDK 1.1.6 /** - * The decimal separator used when formatting currency values. + * The decimal separator string used when formatting currency values. + * @serial + */ + private String monetarySeparatorString; + + /** + * The decimal separator character used when formatting currency values. * @serial * @see #getMonetaryGroupingSeparator */ private char monetaryGroupingSeparator; // Field new in JDK 1.1.6 + /** + * The decimal separator string used when formatting currency values. + * @serial + */ + private String monetaryGroupingSeparatorString; + /** * The character used to distinguish the exponent in a number formatted * in exponential notation, e.g. 'E' for a number such as "1.23E45". @@ -1302,13 +1760,6 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private char padEscape; - /** - * The character used to indicate a plus sign. - * @serial - * @since AlphaWorks - */ - private char plusSign; - /** * The locale for which this object was constructed. Set to the * default locale for objects resurrected from old streams. @@ -1322,14 +1773,6 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { */ private ULocale ulocale; - /** - * String versions of some number symbols. - * @serial - * @since ICU 52 - */ - private String minusString = null; - private String plusString = null; - /** * Exponent multiplication sign. e.g "x" * @serial @@ -1352,6 +1795,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { // - 6 for ICU 4.2, which includes the currencySpc* fields // - 7 for ICU 52, which includes the minusString and plusString fields // - 8 for ICU 54, which includes exponentMultiplicationSign field. + // - 9 for ICU 58, which includes a series of String symbol fields. private static final int currentSerialVersion = 8; /** @@ -1367,7 +1811,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { *

  • 3: Version for ICU 2.2, which adds locale. *
  • 4: Version for ICU 3.2, which adds ulocale. *
  • 5: Version for ICU 3.6, which adds monetaryGroupingSeparator. - *
  • 6: Version for ICU 4.2, which adds currencySpcBeforeSym and + *
  • 6: Version for ICU 4.2, which adds currencySpcBeforeSym and * currencySpcAfterSym. *
  • 7: Version for ICU 52, which adds minusString and plusString. * @@ -1473,10 +1917,10 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { private static class CacheData { final ULocale validLocale; - final char[] digits; + final String[] digits; final String[] numberElements; - public CacheData(ULocale loc, char[] digits, String[] numberElements) { + public CacheData(ULocale loc, String[] digits, String[] numberElements) { validLocale = loc; this.digits = digits; this.numberElements = numberElements; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NumberingSystem.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NumberingSystem.java index 640d0a9a29..fc62629109 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/NumberingSystem.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NumberingSystem.java @@ -17,7 +17,6 @@ import com.ibm.icu.impl.CacheBase; import com.ibm.icu.impl.ICUData; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.SoftCache; -import com.ibm.icu.lang.UCharacter; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale.Category; import com.ibm.icu.util.UResourceBundle; @@ -27,7 +26,7 @@ import com.ibm.icu.util.UResourceBundleIterator; /** * NumberingSystem is the base class for all number * systems. This class provides the interface for setting different numbering - * system types, whether it be a simple alternate digit system such as + * system types, whether it be a simple alternate digit system such as * Thai digits or Devanagari digits, or an algorithmic numbering system such * as Hebrew numbering or Chinese numbering. * @@ -51,7 +50,7 @@ public class NumberingSystem { /** * Factory method for creating a numbering system. - * @param radix_in The radix for this numbering system. ICU currently + * @param radix_in The radix for this numbering system. ICU currently * supports only numbering systems whose radix is 10. * @param isAlgorithmic_in Specifies whether the numbering system is algorithmic * (true) or numeric (false). @@ -66,11 +65,11 @@ public class NumberingSystem { public static NumberingSystem getInstance(int radix_in, boolean isAlgorithmic_in, String desc_in ) { return getInstance(null,radix_in,isAlgorithmic_in,desc_in); } - + /** * Factory method for creating a numbering system. * @param name_in The string representing the name of the numbering system. - * @param radix_in The radix for this numbering system. ICU currently + * @param radix_in The radix for this numbering system. ICU currently * supports only numbering systems whose radix is 10. * @param isAlgorithmic_in Specifies whether the numbering system is algorithmic * (true) or numeric (false). @@ -82,7 +81,7 @@ public class NumberingSystem { * this numbering system. * @stable ICU 4.6 */ - + private static NumberingSystem getInstance(String name_in, int radix_in, boolean isAlgorithmic_in, String desc_in ) { if ( radix_in < 2 ) { throw new IllegalArgumentException("Invalid radix for numbering system"); @@ -252,7 +251,7 @@ public class NumberingSystem { * @stable ICU 4.2 */ public static String [] getAvailableNames() { - + UResourceBundle numberingSystemsInfo = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "numberingSystems"); UResourceBundle nsCurrent = numberingSystemsInfo.get("numberingSystems"); UResourceBundle temp; @@ -269,29 +268,14 @@ public class NumberingSystem { } /** - * Convenience method to determine if a given digit string is valid for use as a + * Convenience method to determine if a given digit string is valid for use as a * descriptor of a numeric ( non-algorithmic ) numbering system. In order for - * a digit string to be valid, it must meet the following criteria: - * 1. Digits must be in Unicode's basic multilingual plane. + * a digit string to be valid, it must contain exactly ten Unicode code points. * @stable ICU 4.2 */ public static boolean isValidDigitString(String str) { - - int c; - int i = 0; - UCharacterIterator it = UCharacterIterator.getInstance(str); - - it.setToStart(); - while ( (c = it.nextCodePoint()) != UCharacterIterator.DONE) { - if ( UCharacter.isSupplementary(c)) { // Digits outside the BMP are not currently supported - return false; - } - i++; - } - if ( i != 10 ) { - return false; - } - return true; + int numCodepoints = str.codePointCount(0, str.length()); + return (numCodepoints == 10); } /** @@ -327,7 +311,7 @@ public class NumberingSystem { * Returns the numbering system's algorithmic status. If true, * the numbering system is algorithmic and uses an RBNF formatter to * format numerals. If false, the numbering system is numeric and - * uses a fixed set of digits. + * uses a fixed set of digits. * @stable ICU 4.2 */ public boolean isAlgorithmic() { diff --git a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java index cb374f6b83..ceabeea4d5 100644 --- a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java +++ b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java @@ -177,8 +177,8 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid crb = crb.at(2); if (crb != null) { String pattern = crb.getString(0); - char separator = crb.getString(1).charAt(0); - char groupingSeparator = crb.getString(2).charAt(0); + String separator = crb.getString(1); + String groupingSeparator = crb.getString(2); return new CurrencyFormatInfo(pattern, separator, groupingSeparator); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java index ae45b1c34c..56688a236b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/IntlTestDecimalFormatSymbols.java @@ -6,11 +6,11 @@ * Corporation and others. All Rights Reserved. **/ -/** +/** * Port From: JDK 1.4b1 : java.text.Format.IntlTestDecimalFormatSymbols * Source File: java/text/format/IntlTestDecimalFormatSymbols.java **/ - + /* @test 1.4 98/03/06 @summary test International Decimal Format Symbols @@ -19,6 +19,7 @@ package com.ibm.icu.dev.test.format; +import java.util.Arrays; import java.util.Locale; import org.junit.Test; @@ -42,7 +43,7 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk } // just do some VERY basic tests to make sure that get/set work - + if(!en.getLocale().equals(Locale.ENGLISH)) { errln("ERROR: getLocale failed"); } @@ -63,8 +64,14 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk errln("ERROR: get/set ZeroDigit failed"); } + String[] digits = en.getDigitStrings(); + fr.setDigitStrings(digits); + if (!Arrays.equals(fr.getDigitStrings(), en.getDigitStrings())) { + errln("ERROR: get/set DigitStrings failed"); + } + char sigDigit = en.getSignificantDigit(); - fr.setSignificantDigit(sigDigit); + fr.setSignificantDigit(sigDigit); if(fr.getSignificantDigit() != en.getSignificantDigit()) { errln("ERROR: get/set SignificantDigit failed"); } @@ -74,47 +81,83 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk if (!fr.getCurrency().equals(currency)){ errln("ERROR: get/set Currency failed"); } - + char group = en.getGroupingSeparator(); fr.setGroupingSeparator(group); if(fr.getGroupingSeparator() != en.getGroupingSeparator()) { errln("ERROR: get/set GroupingSeparator failed"); } + String groupStr = en.getGroupingSeparatorString(); + fr.setGroupingSeparatorString(groupStr); + if (!fr.getGroupingSeparatorString().equals(en.getGroupingSeparatorString())) { + errln("ERROR: get/set GroupingSeparatorString failed"); + } + char decimal = en.getDecimalSeparator(); fr.setDecimalSeparator(decimal); if(fr.getDecimalSeparator() != en.getDecimalSeparator()) { errln("ERROR: get/set DecimalSeparator failed"); } + String decimalStr = en.getDecimalSeparatorString(); + fr.setDecimalSeparatorString(decimalStr); + if (!fr.getDecimalSeparatorString().equals(en.getDecimalSeparatorString())) { + errln("ERROR: get/set DecimalSeparatorString failed"); + } + char monetaryGroup = en.getMonetaryGroupingSeparator(); fr.setMonetaryGroupingSeparator(monetaryGroup); if(fr.getMonetaryGroupingSeparator() != en.getMonetaryGroupingSeparator()) { errln("ERROR: get/set MonetaryGroupingSeparator failed"); } + String monetaryGroupStr = en.getMonetaryGroupingSeparatorString(); + fr.setMonetaryGroupingSeparatorString(monetaryGroupStr); + if (!fr.getMonetaryGroupingSeparatorString().equals(en.getMonetaryGroupingSeparatorString())){ + errln("ERROR: get/set MonetaryGroupingSeparatorString failed"); + } + char monetaryDecimal = en.getMonetaryDecimalSeparator(); fr.setMonetaryDecimalSeparator(monetaryDecimal); if(fr.getMonetaryDecimalSeparator() != en.getMonetaryDecimalSeparator()) { errln("ERROR: get/set MonetaryDecimalSeparator failed"); } + String monetaryDecimalStr = en.getMonetaryDecimalSeparatorString(); + fr.setMonetaryDecimalSeparatorString(monetaryDecimalStr); + if (!fr.getMonetaryDecimalSeparatorString().equals(en.getMonetaryDecimalSeparatorString())) { + errln("ERROR: get/set MonetaryDecimalSeparatorString failed"); + } + char perMill = en.getPerMill(); fr.setPerMill(perMill); if(fr.getPerMill() != en.getPerMill()) { errln("ERROR: get/set PerMill failed"); } + String perMillStr = en.getPerMillString(); + fr.setPerMillString(perMillStr); + if (!fr.getPerMillString().equals(en.getPerMillString())) { + errln("ERROR: get/set PerMillString failed"); + } + char percent = en.getPercent(); fr.setPercent(percent); if(fr.getPercent() != en.getPercent()) { errln("ERROR: get/set Percent failed"); } + String percentStr = en.getPercentString(); + fr.setPercentString(percentStr); + if (!fr.getPercentString().equals(en.getPercentString())) { + errln("ERROR: get/set PercentString failed"); + } + char digit = en.getDigit(); fr.setDigit(digit); - if(fr.getPercent() != en.getPercent()) { - errln("ERROR: get/set Percent failed"); + if(fr.getDigit() != en.getDigit()) { + errln("ERROR: get/set Digit failed"); } char patternSeparator = en.getPatternSeparator(); @@ -143,12 +186,24 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk errln("ERROR: get/set MinusSign failed"); } + String minusSignStr = en.getMinusSignString(); + fr.setMinusSignString(minusSignStr); + if (!fr.getMinusSignString().equals(en.getMinusSignString())) { + errln("ERROR: get/set MinusSignString failed"); + } + char plusSign = en.getPlusSign(); fr.setPlusSign(plusSign); if(fr.getPlusSign() != en.getPlusSign()) { errln("ERROR: get/set PlusSign failed"); } + String plusSignStr = en.getPlusSignString(); + fr.setPlusSignString(plusSignStr); + if (!fr.getPlusSignString().equals(en.getPlusSignString())) { + errln("ERROR: get/set PlusSignString failed"); + } + char padEscape = en.getPadEscape(); fr.setPadEscape(padEscape); if(fr.getPadEscape() != en.getPadEscape()) { @@ -160,15 +215,15 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk if(fr.getExponentSeparator() != en.getExponentSeparator()) { errln("ERROR: get/set Exponential failed"); } - + String exponentMultiplicationSign = en.getExponentMultiplicationSign(); fr.setExponentMultiplicationSign(exponentMultiplicationSign); if(fr.getExponentMultiplicationSign() != en.getExponentMultiplicationSign()) { errln("ERROR: get/set ExponentMultiplicationSign failed"); } - + // Test CurrencySpacing. - // In CLDR 1.7, only root.txt has CurrencySpacing data. This data might + // In CLDR 1.7, only root.txt has CurrencySpacing data. This data might // be different between en and fr in the future. for (int i = DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH; i <= DecimalFormatSymbols.CURRENCY_SPC_INSERT; i++) { if (en.getPatternForCurrencySpacing(i, true) != @@ -180,7 +235,7 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk } } } - + String dash = "-"; en.setPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, true, dash); if (dash != en.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, true)) { @@ -195,13 +250,43 @@ public class IntlTestDecimalFormatSymbols extends com.ibm.icu.dev.test.TestFmwk errln("ERROR: Clone failed"); } } - + @Test public void testCoverage() { DecimalFormatSymbols df = new DecimalFormatSymbols(); DecimalFormatSymbols df2 = (DecimalFormatSymbols)df.clone(); if (!df.equals(df2) || df.hashCode() != df2.hashCode()) { - errln("decimal format symbols clone, equals, or hashCode failed"); + errln("decimal format symbols clone, equals, or hashCode failed"); + } + } + + @Test + public void testDigitSymbols() { + final char defZero = '0'; + final char[] defDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + final String[] defDigitStrings = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + final String[] osmanyaDigitStrings = { + "\uD801\uDCA0", "\uD801\uDCA1", "\uD801\uDCA2", "\uD801\uDCA3", "\uD801\uDCA4", + "\uD801\uDCA5", "\uD801\uDCA6", "\uD801\uDCA7", "\uD801\uDCA8", "\uD801\uDCA9" + }; + + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); + + symbols.setDigitStrings(osmanyaDigitStrings); + if (!Arrays.equals(symbols.getDigitStrings(), osmanyaDigitStrings)) { + errln("ERROR: Osmanya digits (supplementary) should be set"); + } + if (defZero != symbols.getZeroDigit()) { + errln("ERROR: Zero digit should be 0"); + } + if (!Arrays.equals(symbols.getDigits(), defDigits)) { + errln("ERROR: Char digits should be Latin digits"); + } + + // Reset digits to Latin + symbols.setZeroDigit(defZero); + if (!Arrays.equals(symbols.getDigitStrings(), defDigitStrings)) { + errln("ERROR: Latin digits should be set" + symbols.getDigitStrings()[0]); } } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 8f36372b8c..3bc904689f 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -53,9 +53,9 @@ import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; public class NumberFormatTest extends TestFmwk { - + private static ULocale EN = new ULocale("en"); - + private static Number toNumber(String s) { if (s.equals("NaN")) { return Double.NaN; @@ -66,13 +66,13 @@ public class NumberFormatTest extends TestFmwk { } return new BigDecimal(s); } - - + + private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { @Override public Character Id() { return 'J'; } - + @Override public String format(NumberFormatTestData tuple) { DecimalFormat fmt = newDecimalFormat(tuple); @@ -126,7 +126,7 @@ public class NumberFormatTest extends TestFmwk { } return null; } - + @Override public String parseCurrency(NumberFormatTestData tuple) { DecimalFormat fmt = newDecimalFormat(tuple); @@ -272,12 +272,12 @@ public class NumberFormatTest extends TestFmwk { } }; - + private DataDrivenNumberFormatTestUtility.CodeUnderTest JDK = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { @Override public Character Id() { return 'K'; } - + @Override public String format(NumberFormatTestData tuple) { java.text.DecimalFormat fmt = newDecimalFormat(tuple); @@ -331,8 +331,8 @@ public class NumberFormatTest extends TestFmwk { } return null; } - - + + /** * @param tuple @@ -437,7 +437,7 @@ public class NumberFormatTest extends TestFmwk { if (tuple.localizedPattern != null) { fmt.applyLocalizedPattern(tuple.localizedPattern); } - + // lenient parsing not supported by JDK if (tuple.parseIntegerOnly != null) { fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); @@ -455,7 +455,7 @@ public class NumberFormatTest extends TestFmwk { public void TestRoundingScientific10542() { DecimalFormat format = new DecimalFormat("0.00E0"); - + int[] roundingModes = { BigDecimal.ROUND_CEILING, BigDecimal.ROUND_DOWN, @@ -472,7 +472,7 @@ public class NumberFormatTest extends TestFmwk { "Round half even", "Round half up", "Round up"}; - + double[] values = {-0.003006, -0.003005, -0.003004, 0.003014, 0.003015, 0.003016}; // The order of these expected values correspond to the order of roundingModes and the order of values. String[][] expected = { @@ -1048,22 +1048,22 @@ public class NumberFormatTest extends TestFmwk { // format result using CURRENCYSTYLE, // format result using ISOCURRENCYSTYLE, // format result using PLURALCURRENCYSTYLE, - {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollars"}, - {"en_US", "1234.56", "USD", "$1,234.56", "USD1,234.56", "1,234.56 US dollars"}, - {"en_US", "-1234.56", "USD", "-$1,234.56", "-USD1,234.56", "-1,234.56 US dollars"}, - {"zh_CN", "1", "USD", "US$1.00", "USD1.00", "1.00美元"}, - {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD1,234.56", "1,234.56美元"}, - {"zh_CN", "1", "CNY", "¥1.00", "CNY1.00", "1.00人民币"}, - {"zh_CN", "1234.56", "CNY", "¥1,234.56", "CNY1,234.56", "1,234.56人民币"}, - {"ru_RU", "1", "RUB", "1,00 \u20BD", "1,00 RUB", "1,00 российского рубля"}, - {"ru_RU", "2", "RUB", "2,00 \u20BD", "2,00 RUB", "2,00 российского рубля"}, - {"ru_RU", "5", "RUB", "5,00 \u20BD", "5,00 RUB", "5,00 российского рубля"}, - // test locale without currency information - {"root", "-1.23", "USD", "-US$ 1.23", "-USD 1.23", "-1.23 USD"}, - {"root@numbers=latn", "-1.23", "USD", "-US$ 1.23", "-USD 1.23", "-1.23 USD"}, // ensure that the root locale is still used with modifiers + {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollars"}, + {"en_US", "1234.56", "USD", "$1,234.56", "USD1,234.56", "1,234.56 US dollars"}, + {"en_US", "-1234.56", "USD", "-$1,234.56", "-USD1,234.56", "-1,234.56 US dollars"}, + {"zh_CN", "1", "USD", "US$1.00", "USD1.00", "1.00美元"}, + {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD1,234.56", "1,234.56美元"}, + {"zh_CN", "1", "CNY", "¥1.00", "CNY1.00", "1.00人民币"}, + {"zh_CN", "1234.56", "CNY", "¥1,234.56", "CNY1,234.56", "1,234.56人民币"}, + {"ru_RU", "1", "RUB", "1,00 \u20BD", "1,00 RUB", "1,00 российского рубля"}, + {"ru_RU", "2", "RUB", "2,00 \u20BD", "2,00 RUB", "2,00 российского рубля"}, + {"ru_RU", "5", "RUB", "5,00 \u20BD", "5,00 RUB", "5,00 российского рубля"}, + // test locale without currency information + {"root", "-1.23", "USD", "-US$ 1.23", "-USD 1.23", "-1.23 USD"}, + {"root@numbers=latn", "-1.23", "USD", "-US$ 1.23", "-USD 1.23", "-1.23 USD"}, // ensure that the root locale is still used with modifiers {"root@numbers=arab", "-1.23", "USD", "\u200F-\u0661\u066B\u0662\u0663\u00A0US$", "\u200F-\u0661\u066B\u0662\u0663\u00A0USD", "\u200F-\u0661\u066B\u0662\u0663 USD"}, // ensure that the root locale is still used with modifiers - {"es_AR", "1", "INR", "INR\u00A01,00", "INR\u00A01,00", "1,00 rupia india"}, - {"ar_EG", "1", "USD", "١٫٠٠\u00A0US$", "١٫٠٠\u00A0USD", "١٫٠٠ دولار أمريكي"}, + {"es_AR", "1", "INR", "INR\u00A01,00", "INR\u00A01,00", "1,00 rupia india"}, + {"ar_EG", "1", "USD", "١٫٠٠\u00A0US$", "١٫٠٠\u00A0USD", "١٫٠٠ دولار أمريكي"}, }; for (int i=0; i 0); - assertEquals("Currency should be correct.", expected, currencyAmount.getCurrency()); + assertEquals("Currency should be correct.", expected, currencyAmount.getCurrency()); } @Test @@ -3379,7 +3380,7 @@ public class NumberFormatTest extends TestFmwk { result = rbf.format(new BigDecimal(2000.43), sb, new FieldPosition(0)).toString(); if (!"two thousand point four three".equals(result)) { errln("DecimalFormat failed. Expected: 'two thousand point four three' - Actual: '" + result + "'"); - } + } } /* @@ -3900,6 +3901,7 @@ public class NumberFormatTest extends TestFmwk { this.result = result; } + @Override public void run() { for (int i = 0; i < result.length; i++) { AttributedCharacterIterator acitr = fmt.formatToCharacterIterator(num); @@ -4001,7 +4003,7 @@ public class NumberFormatTest extends TestFmwk { ": ArithmeticException must be thrown, but got formatted result: " + s); } else { - assertEquals("Test case #" + testNum, (String)testCase[5], s); + assertEquals("Test case #" + testNum, testCase[5], s); } } @@ -4067,7 +4069,7 @@ public class NumberFormatTest extends TestFmwk { numberFormat.setSignificantDigitsUsed(false); numberFormat.setMaximumSignificantDigits(6); - assertTrue("", numberFormat.areSignificantDigitsUsed()); + assertTrue("", numberFormat.areSignificantDigitsUsed()); } @Test @@ -4177,7 +4179,7 @@ public class NumberFormatTest extends TestFmwk { new SignsAndMarksItem("en@numbers=arabext", false, "\u200E-\u200E\u06F6\u06F7", -67 ), new SignsAndMarksItem("en@numbers=arabext", true, "\u200E-\u200E\u06F6\u06F7", -67 ), new SignsAndMarksItem("en@numbers=arabext", true, "\u200E-\u200E \u06F6\u06F7", -67 ), // *** - + new SignsAndMarksItem("he", false, "12", 12 ), new SignsAndMarksItem("he", true, "12", 12 ), new SignsAndMarksItem("he", false, "-23", -23 ), @@ -4298,7 +4300,7 @@ public class NumberFormatTest extends TestFmwk { expect(fmtAccount, num, fmtAccountExpected, rt); } } - + @Test public void TestCurrencyUsage() { // the 1st one is checking setter/getter, while the 2nd one checks for getInstance @@ -4379,41 +4381,41 @@ public class NumberFormatTest extends TestFmwk { @Test public void TestParseRequiredDecimalPoint() { - + String[] testPattern = { "00.####", "00.0", "00" }; - + String value2Parse = "99"; double parseValue = 99; DecimalFormat parser = new DecimalFormat(); double result; - boolean hasDecimalPoint; - for (int i = 0; i < testPattern.length; i++) { + boolean hasDecimalPoint; + for (int i = 0; i < testPattern.length; i++) { parser.applyPattern(testPattern[i]); hasDecimalPoint = testPattern[i].contains("."); - + parser.setDecimalPatternMatchRequired(false); try { result = parser.parse(value2Parse).doubleValue(); assertEquals("wrong parsed value", parseValue, result); } catch (ParseException e) { - TestFmwk.errln("Parsing " + value2Parse + " should have succeeded with " + testPattern[i] + + TestFmwk.errln("Parsing " + value2Parse + " should have succeeded with " + testPattern[i] + " and isDecimalPointMatchRequired set to: " + parser.isDecimalPatternMatchRequired()); } - + parser.setDecimalPatternMatchRequired(true); try { result = parser.parse(value2Parse).doubleValue(); if(hasDecimalPoint){ - TestFmwk.errln("Parsing " + value2Parse + " should NOT have succeeded with " + testPattern[i] + + TestFmwk.errln("Parsing " + value2Parse + " should NOT have succeeded with " + testPattern[i] + " and isDecimalPointMatchRequired set to: " + parser.isDecimalPatternMatchRequired()); } } catch (ParseException e) { // OK, should fail } } - + } - + //TODO(junit): investigate @Test @@ -4457,7 +4459,7 @@ public class NumberFormatTest extends TestFmwk { // For each test, add assert that all the fields are present and in the right spot. // TODO: Add tests for identify and position of each field, as in IntlTestDecimalFormatAPIC. - + DecimalFormat dfDE = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.GERMANY); String strDE = dfDE.format(number); Set resultDE = dfDE.formatToCharacterIterator(number).getAllAttributeKeys(); @@ -4511,7 +4513,7 @@ public class NumberFormatTest extends TestFmwk { StringBuffer result = new StringBuffer(""); df.setRoundingMode(BigDecimal.ROUND_UNNECESSARY); df.applyPattern("00.0#E0"); - + try { df.format(99999.0, result, new FieldPosition(0)); fail("Missing ArithmeticException for double: " + result); @@ -4539,7 +4541,7 @@ public class NumberFormatTest extends TestFmwk { } catch (ArithmeticException expected) { // The exception should be thrown, since rounding is needed. } - + try { result = df.format(new BigDecimal("-99999"), result, new FieldPosition(0)); fail("Missing ArithmeticException for BigDecimal: " + result); @@ -4885,4 +4887,18 @@ public class NumberFormatTest extends TestFmwk { checkFormatWithField("exponent", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.EXPONENT, 9, 11); } + + @Test + public void TestStringSymbols() { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(ULocale.US); + + String[] customDigits = {"(0)", "(1)", "(2)", "(3)", "(4)", "(5)", "(6)", "(7)", "(8)", "(9)"}; + symbols.setDigitStrings(customDigits); + symbols.setDecimalSeparatorString("~~"); + symbols.setGroupingSeparatorString("^^"); + + DecimalFormat fmt = new DecimalFormat("#,##0.0#", symbols); + + expect2(fmt, 1234567.89, "(1)^^(2)(3)(4)^^(5)(6)(7)~~(8)(9)"); + } }