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 {
*
+ * 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 {
*
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