ICU-13113 Changing decimal format exceptions to always contain the string "Malformed pattern" for better backwards and forwards compatibility.
X-SVN-Rev: 40027
This commit is contained in:
parent
990a7b0c62
commit
39184c68c2
@ -19,18 +19,19 @@ public class PatternString {
|
||||
* @param pattern The pattern string, like "#,##0.00"
|
||||
* @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding
|
||||
* increment) when parsing the pattern. This may be desirable if a custom rounding mode, such
|
||||
* as CurrencyUsage, is to be used instead.
|
||||
* as CurrencyUsage, is to be used instead. One of {@link #IGNORE_ROUNDING_ALWAYS}, {@link
|
||||
* #IGNORE_ROUNDING_IF_CURRENCY}, or {@link #IGNORE_ROUNDING_NEVER}.
|
||||
* @return A property bag object.
|
||||
* @throws IllegalArgumentException If there is a syntax error in the pattern string.
|
||||
*/
|
||||
public static Properties parseToProperties(String pattern, boolean ignoreRounding) {
|
||||
public static Properties parseToProperties(String pattern, int ignoreRounding) {
|
||||
Properties properties = new Properties();
|
||||
LdmlDecimalPatternParser.parse(pattern, properties, ignoreRounding);
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static Properties parseToProperties(String pattern) {
|
||||
return parseToProperties(pattern, false);
|
||||
return parseToProperties(pattern, PatternString.IGNORE_ROUNDING_NEVER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,16 +44,17 @@ public class PatternString {
|
||||
* @param properties The property bag object to overwrite.
|
||||
* @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding
|
||||
* increment) when parsing the pattern. This may be desirable if a custom rounding mode, such
|
||||
* as CurrencyUsage, is to be used instead.
|
||||
* as CurrencyUsage, is to be used instead. One of {@link #IGNORE_ROUNDING_ALWAYS}, {@link
|
||||
* #IGNORE_ROUNDING_IF_CURRENCY}, or {@link #IGNORE_ROUNDING_NEVER}.
|
||||
* @throws IllegalArgumentException If there was a syntax error in the pattern string.
|
||||
*/
|
||||
public static void parseToExistingProperties(
|
||||
String pattern, Properties properties, boolean ignoreRounding) {
|
||||
String pattern, Properties properties, int ignoreRounding) {
|
||||
LdmlDecimalPatternParser.parse(pattern, properties, ignoreRounding);
|
||||
}
|
||||
|
||||
public static void parseToExistingProperties(String pattern, Properties properties) {
|
||||
parseToExistingProperties(pattern, properties, false);
|
||||
parseToExistingProperties(pattern, properties, PatternString.IGNORE_ROUNDING_NEVER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -384,6 +386,10 @@ public class PatternString {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static final int IGNORE_ROUNDING_NEVER = 0;
|
||||
public static final int IGNORE_ROUNDING_IF_CURRENCY = 1;
|
||||
public static final int IGNORE_ROUNDING_ALWAYS = 2;
|
||||
|
||||
/** Implements a recursive descent parser for decimal format patterns. */
|
||||
static class LdmlDecimalPatternParser {
|
||||
|
||||
@ -396,10 +402,20 @@ public class PatternString {
|
||||
SubpatternParseResult negative = null;
|
||||
|
||||
/** Finalizes the temporary data stored in the PatternParseResult to the Builder. */
|
||||
void saveToProperties(Properties properties, boolean ignoreRounding) {
|
||||
void saveToProperties(Properties properties, int _ignoreRounding) {
|
||||
// Translate from PatternState to Properties.
|
||||
// Note that most data from "negative" is ignored per the specification of DecimalFormat.
|
||||
|
||||
boolean ignoreRounding;
|
||||
if (_ignoreRounding == IGNORE_ROUNDING_NEVER) {
|
||||
ignoreRounding = false;
|
||||
} else if (_ignoreRounding == IGNORE_ROUNDING_IF_CURRENCY) {
|
||||
ignoreRounding = positive.hasCurrencySign;
|
||||
} else {
|
||||
assert _ignoreRounding == IGNORE_ROUNDING_ALWAYS;
|
||||
ignoreRounding = true;
|
||||
}
|
||||
|
||||
// Grouping settings
|
||||
if (positive.groupingSizes[1] != -1) {
|
||||
properties.setGroupingSize(positive.groupingSizes[0]);
|
||||
@ -556,6 +572,7 @@ public class PatternString {
|
||||
int exponentDigits = 0;
|
||||
boolean hasPercentSign = false;
|
||||
boolean hasPerMilleSign = false;
|
||||
boolean hasCurrencySign = false;
|
||||
|
||||
StringBuilder padding = new StringBuilder();
|
||||
StringBuilder prefix = new StringBuilder();
|
||||
@ -588,23 +605,17 @@ public class PatternString {
|
||||
|
||||
IllegalArgumentException toParseException(String message) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Unexpected character in decimal format pattern: '");
|
||||
sb.append("Malformed pattern for ICU DecimalFormat: \"");
|
||||
sb.append(pattern);
|
||||
sb.append("': ");
|
||||
sb.append("\": ");
|
||||
sb.append(message);
|
||||
sb.append(": ");
|
||||
if (peek() == -1) {
|
||||
sb.append("EOL");
|
||||
} else {
|
||||
sb.append("'");
|
||||
sb.append(Character.toChars(peek()));
|
||||
sb.append("'");
|
||||
}
|
||||
sb.append(" at position ");
|
||||
sb.append(offset);
|
||||
return new IllegalArgumentException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void parse(String pattern, Properties properties, boolean ignoreRounding) {
|
||||
static void parse(String pattern, Properties properties, int ignoreRounding) {
|
||||
if (pattern == null || pattern.length() == 0) {
|
||||
// Backwards compatibility requires that we reset to the default values.
|
||||
// TODO: Only overwrite the properties that "saveToProperties" normally touches?
|
||||
@ -629,7 +640,7 @@ public class PatternString {
|
||||
consumeSubpattern(state, result.negative);
|
||||
}
|
||||
if (state.peek() != -1) {
|
||||
throw state.toParseException("pattern");
|
||||
throw state.toParseException("Found unquoted special character");
|
||||
}
|
||||
}
|
||||
|
||||
@ -689,7 +700,7 @@ public class PatternString {
|
||||
break;
|
||||
|
||||
case '¤':
|
||||
// no need to record that we saw it
|
||||
result.hasCurrencySign = true;
|
||||
break;
|
||||
}
|
||||
consumeLiteral(state, destination);
|
||||
@ -698,12 +709,12 @@ public class PatternString {
|
||||
|
||||
private static void consumeLiteral(ParserState state, StringBuilder destination) {
|
||||
if (state.peek() == -1) {
|
||||
throw state.toParseException("expected unquoted literal but found end of string");
|
||||
throw state.toParseException("Expected unquoted literal but found EOL");
|
||||
} else if (state.peek() == '\'') {
|
||||
destination.appendCodePoint(state.next()); // consume the starting quote
|
||||
while (state.peek() != '\'') {
|
||||
if (state.peek() == -1) {
|
||||
throw state.toParseException("expected quoted literal but found end of string");
|
||||
throw state.toParseException("Expected quoted literal but found EOL");
|
||||
} else {
|
||||
destination.appendCodePoint(state.next()); // consume a quoted character
|
||||
}
|
||||
@ -751,7 +762,7 @@ public class PatternString {
|
||||
|
||||
case '@':
|
||||
seenSignificantDigitMarker = true;
|
||||
if (seenDigit) throw state.toParseException("Can't mix @ and 0 in pattern");
|
||||
if (seenDigit) throw state.toParseException("Cannot mix 0 and @");
|
||||
result.paddingWidth += 1;
|
||||
result.groupingSizes[0] += 1;
|
||||
result.totalIntegerDigits += 1;
|
||||
@ -772,8 +783,7 @@ public class PatternString {
|
||||
case '8':
|
||||
case '9':
|
||||
seenDigit = true;
|
||||
if (seenSignificantDigitMarker)
|
||||
throw state.toParseException("Can't mix @ and 0 in pattern");
|
||||
if (seenSignificantDigitMarker) throw state.toParseException("Cannot mix @ and 0");
|
||||
// TODO: Crash here if we've seen the significant digit marker? See NumberFormatTestCases.txt
|
||||
result.paddingWidth += 1;
|
||||
result.groupingSizes[0] += 1;
|
||||
|
@ -219,7 +219,8 @@ public class CurrencyFormat {
|
||||
} else {
|
||||
// CurrencyPluralInfo is available. Use it to generate affixes for long name support.
|
||||
String pluralPattern = info.getCurrencyPluralPattern(plural.getKeyword());
|
||||
PatternString.parseToExistingProperties(pluralPattern, temp, true);
|
||||
PatternString.parseToExistingProperties(
|
||||
pluralPattern, temp, PatternString.IGNORE_ROUNDING_ALWAYS);
|
||||
result = pnag.getModifiers(symbols, sym, iso, longName, temp);
|
||||
}
|
||||
mod.put(plural, result.positive, result.negative);
|
||||
|
@ -12,6 +12,7 @@ package com.ibm.icu.text;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.ibm.icu.impl.number.PatternString;
|
||||
import com.ibm.icu.impl.number.Properties;
|
||||
import com.ibm.icu.util.CurrencyAmount;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
@ -106,7 +107,7 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
properties = new Properties();
|
||||
properties.setCompactStyle(style);
|
||||
exportedProperties = new Properties();
|
||||
setPropertiesFromPattern(pattern, true);
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_NEVER);
|
||||
if (style == CompactStyle.SHORT) {
|
||||
// TODO: This was setGroupingUsed(false) in ICU 58. Is it okay that I changed it for ICU 59?
|
||||
properties.setMinimumGroupingDigits(2);
|
||||
|
@ -13,7 +13,6 @@ import java.text.FieldPosition;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
|
||||
import com.ibm.icu.impl.number.AffixPatternUtils;
|
||||
import com.ibm.icu.impl.number.Endpoint;
|
||||
import com.ibm.icu.impl.number.Format.SingularFormat;
|
||||
import com.ibm.icu.impl.number.FormatQuantity4;
|
||||
@ -302,8 +301,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
properties = new Properties();
|
||||
exportedProperties = new Properties();
|
||||
// Regression: ignore pattern rounding information if the pattern has currency symbols.
|
||||
boolean ignorePatternRounding = AffixPatternUtils.hasCurrencySymbols(pattern);
|
||||
setPropertiesFromPattern(pattern, ignorePatternRounding);
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
refreshFormatter();
|
||||
}
|
||||
|
||||
@ -332,8 +330,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
properties = new Properties();
|
||||
exportedProperties = new Properties();
|
||||
// Regression: ignore pattern rounding information if the pattern has currency symbols.
|
||||
boolean ignorePatternRounding = AffixPatternUtils.hasCurrencySymbols(pattern);
|
||||
setPropertiesFromPattern(pattern, ignorePatternRounding);
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
refreshFormatter();
|
||||
}
|
||||
|
||||
@ -362,8 +359,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
properties = new Properties();
|
||||
exportedProperties = new Properties();
|
||||
// Regression: ignore pattern rounding information if the pattern has currency symbols.
|
||||
boolean ignorePatternRounding = AffixPatternUtils.hasCurrencySymbols(pattern);
|
||||
setPropertiesFromPattern(pattern, ignorePatternRounding);
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
refreshFormatter();
|
||||
}
|
||||
|
||||
@ -405,11 +401,10 @@ public class DecimalFormat extends NumberFormat {
|
||||
|| choice == ACCOUNTINGCURRENCYSTYLE
|
||||
|| choice == CASHCURRENCYSTYLE
|
||||
|| choice == STANDARDCURRENCYSTYLE
|
||||
|| choice == PLURALCURRENCYSTYLE
|
||||
|| AffixPatternUtils.hasCurrencySymbols(pattern)) {
|
||||
setPropertiesFromPattern(pattern, true);
|
||||
|| choice == PLURALCURRENCYSTYLE) {
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_ALWAYS);
|
||||
} else {
|
||||
setPropertiesFromPattern(pattern, false);
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
}
|
||||
refreshFormatter();
|
||||
}
|
||||
@ -450,7 +445,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
public synchronized void applyPattern(String pattern) {
|
||||
setPropertiesFromPattern(pattern, false);
|
||||
setPropertiesFromPattern(pattern, PatternString.IGNORE_ROUNDING_NEVER);
|
||||
// Backwards compatibility: clear out user-specified prefix and suffix,
|
||||
// as well as CurrencyPluralInfo.
|
||||
properties.setPositivePrefix(null);
|
||||
@ -2447,11 +2442,14 @@ public class DecimalFormat extends NumberFormat {
|
||||
* Updates the property bag with settings from the given pattern.
|
||||
*
|
||||
* @param pattern The pattern string to parse.
|
||||
* @param ignoreRounding Whether to read rounding information from the string. Set to false if
|
||||
* CurrencyUsage is to be used instead.
|
||||
* @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding
|
||||
* increment) when parsing the pattern. This may be desirable if a custom rounding mode, such
|
||||
* as CurrencyUsage, is to be used instead. One of {@link
|
||||
* PatternString#IGNORE_ROUNDING_ALWAYS}, {@link PatternString#IGNORE_ROUNDING_IF_CURRENCY},
|
||||
* or {@link PatternString#IGNORE_ROUNDING_NEVER}.
|
||||
* @see PatternString#parseToExistingProperties
|
||||
*/
|
||||
void setPropertiesFromPattern(String pattern, boolean ignoreRounding) {
|
||||
void setPropertiesFromPattern(String pattern, int ignoreRounding) {
|
||||
PatternString.parseToExistingProperties(pattern, properties, ignoreRounding);
|
||||
}
|
||||
|
||||
|
@ -448,7 +448,12 @@ public class NumberFormatDataDrivenTest {
|
||||
public String format(DataDrivenNumberFormatTestData tuple) {
|
||||
String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
|
||||
ULocale locale = (tuple.locale == null) ? ULocale.ENGLISH : tuple.locale;
|
||||
Properties properties = PatternString.parseToProperties(pattern, tuple.currency != null);
|
||||
Properties properties =
|
||||
PatternString.parseToProperties(
|
||||
pattern,
|
||||
tuple.currency != null
|
||||
? PatternString.IGNORE_ROUNDING_ALWAYS
|
||||
: PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
propertiesFromTuple(tuple, properties);
|
||||
Format fmt = Endpoint.fromBTA(properties, locale);
|
||||
FormatQuantity q1, q2, q3;
|
||||
@ -513,7 +518,12 @@ public class NumberFormatDataDrivenTest {
|
||||
final Properties properties;
|
||||
DecimalFormat df;
|
||||
try {
|
||||
properties = PatternString.parseToProperties(pattern, tuple.currency != null);
|
||||
properties =
|
||||
PatternString.parseToProperties(
|
||||
pattern,
|
||||
tuple.currency != null
|
||||
? PatternString.IGNORE_ROUNDING_ALWAYS
|
||||
: PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
propertiesFromTuple(tuple, properties);
|
||||
// TODO: Use PatternString.propertiesToString() directly. (How to deal with CurrencyUsage?)
|
||||
df = new DecimalFormat();
|
||||
@ -559,7 +569,12 @@ public class NumberFormatDataDrivenTest {
|
||||
ParsePosition ppos = new ParsePosition(0);
|
||||
Number actual;
|
||||
try {
|
||||
properties = PatternString.parseToProperties(pattern, tuple.currency != null);
|
||||
properties =
|
||||
PatternString.parseToProperties(
|
||||
pattern,
|
||||
tuple.currency != null
|
||||
? PatternString.IGNORE_ROUNDING_ALWAYS
|
||||
: PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
propertiesFromTuple(tuple, properties);
|
||||
actual =
|
||||
Parse.parse(
|
||||
@ -613,7 +628,12 @@ public class NumberFormatDataDrivenTest {
|
||||
ParsePosition ppos = new ParsePosition(0);
|
||||
CurrencyAmount actual;
|
||||
try {
|
||||
properties = PatternString.parseToProperties(pattern, tuple.currency != null);
|
||||
properties =
|
||||
PatternString.parseToProperties(
|
||||
pattern,
|
||||
tuple.currency != null
|
||||
? PatternString.IGNORE_ROUNDING_ALWAYS
|
||||
: PatternString.IGNORE_ROUNDING_IF_CURRENCY);
|
||||
propertiesFromTuple(tuple, properties);
|
||||
actual =
|
||||
Parse.parseCurrency(
|
||||
|
@ -4992,6 +4992,31 @@ public class NumberFormatTest extends TestFmwk {
|
||||
expect2(numfmt, num, "٪ −۱٬۲۳۴");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Test13113() {
|
||||
String[][] cases = {
|
||||
{"'", "quoted literal"},
|
||||
{"ab#c'd", "quoted literal"},
|
||||
{"ab#c*", "unquoted literal"},
|
||||
{"0#", "# cannot follow 0"},
|
||||
{".#0", "0 cannot follow #"},
|
||||
{"@0", "Cannot mix @ and 0"},
|
||||
{"0@", "Cannot mix 0 and @"},
|
||||
{"#x#", "unquoted special character"}
|
||||
};
|
||||
for (String[] cas : cases) {
|
||||
try {
|
||||
new DecimalFormat(cas[0]);
|
||||
fail("Should have thrown on malformed pattern");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
assertTrue("Exception should contain \"Malformed pattern\": " + ex.getMessage(),
|
||||
ex.getMessage().contains("Malformed pattern"));
|
||||
assertTrue("Exception should contain \"" + cas[1] + "\"" + ex.getMessage(),
|
||||
ex.getMessage().contains(cas[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Test13118() {
|
||||
DecimalFormat df = new DecimalFormat("@@@");
|
||||
|
Loading…
Reference in New Issue
Block a user