ICU-13060 Assorted test cases, @internal tags, and fixes for DecimalFormat.

X-SVN-Rev: 39908
This commit is contained in:
Shane Carr 2017-03-22 23:30:42 +00:00
parent e5ea539acf
commit a47756d190
15 changed files with 545 additions and 224 deletions

View file

@ -13,13 +13,13 @@ import java.util.concurrent.ConcurrentHashMap;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.TextTrieMap;
import com.ibm.icu.impl.number.Parse.ParseMode;
import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
import com.ibm.icu.impl.number.formatters.CurrencyFormat;
import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
import com.ibm.icu.impl.number.formatters.PaddingFormat;
import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
import com.ibm.icu.impl.number.formatters.ScientificFormat;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.CurrencyPluralInfo;
import com.ibm.icu.text.DecimalFormatSymbols;
@ -102,7 +102,8 @@ public class Parse {
CurrencyFormat.ICurrencyProperties,
BigDecimalMultiplier.IProperties,
MagnitudeMultiplier.IProperties,
PositiveDecimalFormat.IProperties {
PositiveDecimalFormat.IProperties,
ScientificFormat.IProperties {
boolean DEFAULT_PARSE_INTEGER_ONLY = false;
@ -199,7 +200,7 @@ public class Parse {
}
/**
* @see #parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
* @see Parse#parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
* DecimalFormatSymbols)
*/
private static enum StateName {
@ -303,6 +304,7 @@ public class Parse {
boolean sawPrefix;
boolean sawSuffix;
boolean sawDecimalPoint;
boolean sawExponentDigit;
// Data for intermediate parsing steps:
StateName returnTo1;
@ -310,6 +312,7 @@ public class Parse {
// For string literals:
CharSequence currentString;
int currentOffset;
boolean currentTrailing;
// For affix patterns:
CharSequence currentAffixPattern;
long currentStepwiseParserTag;
@ -349,12 +352,14 @@ public class Parse {
sawPrefix = false;
sawSuffix = false;
sawDecimalPoint = false;
sawExponentDigit = false;
// Data for intermediate parsing steps:
returnTo1 = null;
returnTo2 = null;
currentString = null;
currentOffset = 0;
currentTrailing = false;
currentAffixPattern = null;
currentStepwiseParserTag = 0L;
currentCurrencyTrieState = null;
@ -404,12 +409,14 @@ public class Parse {
sawPrefix = other.sawPrefix;
sawSuffix = other.sawSuffix;
sawDecimalPoint = other.sawDecimalPoint;
sawExponentDigit = other.sawExponentDigit;
// Data for intermediate parsing steps:
returnTo1 = other.returnTo1;
returnTo2 = other.returnTo2;
currentString = other.currentString;
currentOffset = other.currentOffset;
currentTrailing = other.currentTrailing;
currentAffixPattern = other.currentAffixPattern;
currentStepwiseParserTag = other.currentStepwiseParserTag;
currentCurrencyTrieState = other.currentCurrencyTrieState;
@ -427,6 +434,7 @@ public class Parse {
*/
void appendDigit(byte digit, DigitType type) {
if (type == DigitType.EXPONENT) {
sawExponentDigit = true;
int newExponent = exponent * 10 + digit;
if (newExponent < exponent) {
// overflow
@ -606,7 +614,11 @@ public class Parse {
int groupingCp2;
SeparatorType decimalType1;
SeparatorType decimalType2;
// TODO(sffc): Remove this field if it is not necessary.
@SuppressWarnings("unused")
SeparatorType groupingType1;
// TODO(sffc): Remove this field if it is not necessary.
@SuppressWarnings("unused")
SeparatorType groupingType2;
TextTrieMap<Byte> digitTrie;
Set<AffixHolder> affixHolders = new HashSet<AffixHolder>();
@ -1299,6 +1311,12 @@ public class Parse {
continue;
}
// Check for scientific notation.
if (properties.getMinimumExponentDigits() > 0 && !item.sawExponentDigit) {
if (DEBUGGING) System.out.println("-> reject due to lack of exponent");
continue;
}
// Check that grouping sizes are valid.
int grouping1 = properties.getGroupingSize();
int grouping2 = properties.getSecondaryGroupingSize();
@ -1621,7 +1639,7 @@ public class Parse {
private static void acceptNan(int cp, StateName nextName, ParserState state, StateItem item) {
CharSequence nan = state.symbols.getNaN();
long added = acceptString(cp, nextName, null, state, item, nan, 0);
long added = acceptString(cp, nextName, null, state, item, nan, 0, false);
// Set state in the items that were added by the function call
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
@ -1634,7 +1652,7 @@ public class Parse {
private static void acceptInfinity(
int cp, StateName nextName, ParserState state, StateItem item) {
CharSequence inf = state.symbols.getInfinity();
long added = acceptString(cp, nextName, null, state, item, inf, 0);
long added = acceptString(cp, nextName, null, state, item, inf, 0, false);
// Set state in the items that were added by the function call
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
@ -1647,7 +1665,7 @@ public class Parse {
private static void acceptExponentSeparator(
int cp, StateName nextName, ParserState state, StateItem item) {
CharSequence exp = state.symbols.getExponentSeparator();
acceptString(cp, nextName, null, state, item, exp, 0);
acceptString(cp, nextName, null, state, item, exp, 0, true);
}
private static void acceptPrefix(int cp, StateName nextName, ParserState state, StateItem item) {
@ -1676,7 +1694,7 @@ public class Parse {
if (holder == null) return;
String str = prefix ? holder.p : holder.s;
if (holder.strings) {
long added = acceptString(cp, nextName, null, state, item, str, 0);
long added = acceptString(cp, nextName, null, state, item, str, 0, false);
// At most one item can be added upon consuming a string.
if (added != 0) {
int i = state.lastInsertedIndex();
@ -1705,7 +1723,14 @@ public class Parse {
private static void acceptStringOffset(int cp, ParserState state, StateItem item) {
acceptString(
cp, item.returnTo1, item.returnTo2, state, item, item.currentString, item.currentOffset);
cp,
item.returnTo1,
item.returnTo2,
state,
item,
item.currentString,
item.currentOffset,
item.currentTrailing);
}
/**
@ -1719,6 +1744,8 @@ public class Parse {
* @param returnTo1 The state to return to after reaching the end of the string.
* @param returnTo2 The state to save in <code>returnTo1</code> after reaching the end of the
* string. Set to null if returning to the main state loop.
* @param trailing true if this string should be ignored for the purposes of recording trailing
* code points; false if it trailing count should be reset after reading the string.
* @param state The current {@link ParserState}
* @param item The current {@link StateItem}
* @param str The string against which to check for a match.
@ -1733,7 +1760,8 @@ public class Parse {
ParserState state,
StateItem item,
CharSequence str,
int offset) {
int offset,
boolean trailing) {
if (str == null || str.length() == 0) return 0L;
// Fast path for fast mode
@ -1771,10 +1799,11 @@ public class Parse {
next.returnTo2 = returnTo2;
next.currentString = str;
next.currentOffset = offset;
next.currentTrailing = trailing;
} else {
// We've reached the end of the string.
next.name = returnTo1;
next.trailingCount = 0;
if (!trailing) next.trailingCount = 0;
next.returnTo1 = returnTo2;
next.returnTo2 = null;
}
@ -1832,9 +1861,11 @@ public class Parse {
resolvedPlusSign = true;
break;
case AffixPatternUtils.TYPE_PERCENT:
resolvedCp = '%'; // accept ASCII percent as well as locale percent
resolvedStr = state.symbols.getPercentString();
break;
case AffixPatternUtils.TYPE_PERMILLE:
resolvedCp = '‰'; // accept ASCII permille as well as locale permille
resolvedStr = state.symbols.getPerMillString();
break;
case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
@ -1905,9 +1936,10 @@ public class Parse {
// String symbol
if (hasNext) {
added |=
acceptString(cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0);
acceptString(
cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0, false);
} else {
added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0);
added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0, false);
}
}
if (resolvedCurrency) {
@ -1965,8 +1997,8 @@ public class Parse {
str1 = state.symbols.getCurrencySymbol();
str2 = state.symbols.getInternationalCurrencySymbol();
}
added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0);
added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0);
added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0, false);
added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0, false);
for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
if (((1L << i) & added) != 0) {
state.getItem(i).sawCurrency = true;

View file

@ -556,7 +556,6 @@ public class PatternString {
int exponentDigits = 0;
boolean hasPercentSign = false;
boolean hasPerMilleSign = false;
boolean hasCurrencySign = false;
StringBuilder padding = new StringBuilder();
StringBuilder prefix = new StringBuilder();
@ -690,7 +689,7 @@ public class PatternString {
break;
case '¤':
result.hasCurrencySign = true;
// no need to record that we saw it
break;
}
consumeLiteral(state, destination);

View file

@ -617,7 +617,7 @@ public class Properties
int count = ois.readInt();
// 2) Read each field by its name and value
for (int i=0; i<count; i++) {
for (int i = 0; i < count; i++) {
String name = (String) ois.readObject();
Object value = ois.readObject();
@ -917,6 +917,16 @@ public class Properties
public String toString() {
StringBuilder result = new StringBuilder();
result.append("<Properties");
toStringBare(result);
result.append(">");
return result.toString();
}
/**
* Appends a string containing properties that differ from the default, but without being
* surrounded by &lt;Properties&gt;.
*/
public void toStringBare(StringBuilder result) {
Field[] fields = Properties.class.getDeclaredFields();
for (Field field : fields) {
Object myValue, defaultValue;
@ -938,8 +948,6 @@ public class Properties
result.append(" " + field.getName() + ":" + myValue);
}
}
result.append(">");
return result.toString();
}
/**

View file

@ -230,24 +230,6 @@ public abstract class Rounder extends Format.BeforeFormat {
input.setIntegerFractionLength(minInt, maxInt, minFrac, maxFrac);
}
private static final ThreadLocal<Properties> threadLocalProperties =
new ThreadLocal<Properties>() {
@Override
protected Properties initialValue() {
return new Properties();
}
};
/**
* Gets a thread-local property bag that can be used to deliver properties to a constructor.
* Rounders themselves are guaranteed to not internally use a copy of this property bag.
*
* @return A clean, thread-local property bag.
*/
public static Properties getThreadLocalProperties() {
return threadLocalProperties.get().clear();
}
@Override
public void before(FormatQuantity input, ModifierHolder mods) {
apply(input);

View file

@ -4,6 +4,7 @@ package com.ibm.icu.impl.number.formatters;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
@ -67,16 +68,45 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
return new CompactDecimalFormat(symbols, properties);
}
private static final int DEFAULT_MIN_SIG = 1;
private static final int DEFAULT_MAX_SIG = 2;
private static final SignificantDigitsMode DEFAULT_SIG_MODE =
SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION;
private static final ThreadLocal<Properties> threadLocalProperties =
new ThreadLocal<Properties>() {
@Override
protected Properties initialValue() {
return new Properties();
}
};
private static Rounder getRounder(IProperties properties) {
// Use rounding settings if they were specified, or else use the default CDF rounder.
Rounder rounder = RoundingFormat.getDefaultOrNull(properties);
// TODO: Detecting and overriding significant digits here is a bit of a hack, since detection
// is also performed in the "RoundingFormat.getDefaultOrNull" method.
// It would be more elegant to call some sort of "fallback" copy method.
Rounder rounder = null;
if (!SignificantDigitsRounder.useSignificantDigits(properties)) {
rounder = RoundingFormat.getDefaultOrNull(properties);
}
if (rounder == null) {
rounder =
SignificantDigitsRounder.getInstance(
SignificantDigitsRounder.getThreadLocalProperties()
.setMinimumSignificantDigits(1)
.setMaximumSignificantDigits(2)
.setSignificantDigitsMode(SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION));
int _minSig = properties.getMinimumSignificantDigits();
int _maxSig = properties.getMaximumSignificantDigits();
SignificantDigitsMode _mode = properties.getSignificantDigitsMode();
Properties rprops = threadLocalProperties.get().clear();
// Settings needing possible override:
rprops.setMinimumSignificantDigits(_minSig > 0 ? _minSig : DEFAULT_MIN_SIG);
rprops.setMaximumSignificantDigits(_maxSig > 0 ? _maxSig : DEFAULT_MAX_SIG);
rprops.setSignificantDigitsMode(_mode != null ? _mode : DEFAULT_SIG_MODE);
// TODO: Should copyFrom() be used instead? It requires a cast.
// Settings to copy verbatim:
rprops.setRoundingMode(properties.getRoundingMode());
rprops.setMinimumFractionDigits(properties.getMinimumFractionDigits());
rprops.setMaximumFractionDigits(properties.getMaximumFractionDigits());
rprops.setMinimumIntegerDigits(properties.getMinimumIntegerDigits());
rprops.setMaximumIntegerDigits(properties.getMaximumIntegerDigits());
rounder = SignificantDigitsRounder.getInstance(rprops);
}
return rounder;
}
@ -101,17 +131,31 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
ULocale ulocale = symbols.getULocale();
CompactDecimalDataSink sink = new CompactDecimalDataSink(data, symbols, fingerprint);
String nsName = NumberingSystem.getInstance(ulocale).getName();
ICUResourceBundle r =
ICUResourceBundle rb =
(ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ulocale);
r.getAllItemsWithFallback("NumberElements/" + nsName, sink);
internalPopulateData(nsName, rb, sink, data);
if (data.isEmpty() && fingerprint.compactStyle == CompactStyle.LONG) {
// No long data is available; load short data instead
sink.compactStyle = CompactStyle.SHORT;
internalPopulateData(nsName, rb, sink, data);
}
threadLocalDataCache.get().put(fingerprint, data);
return data;
}
private static void internalPopulateData(
String nsName, ICUResourceBundle rb, CompactDecimalDataSink sink, CompactDecimalData data) {
try {
rb.getAllItemsWithFallback("NumberElements/" + nsName, sink);
} catch (MissingResourceException e) {
// Fall back to latn
}
if (data.isEmpty() && !nsName.equals("latn")) {
r.getAllItemsWithFallback("NumberElements/latn", sink);
rb.getAllItemsWithFallback("NumberElements/latn", sink);
}
if (sink.exception != null) {
throw sink.exception;
}
threadLocalDataCache.get().put(fingerprint, data);
return data;
}
private static PositiveNegativeModifier getDefaultMod(
@ -302,7 +346,7 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
currencySymbol = CurrencyFormat.getCurrencySymbol(symbols, properties);
} else {
compactType = CompactType.DECIMAL;
currencySymbol = symbols.getCurrencySymbol(); // fallback; should remain unused
currencySymbol = ""; // fallback; should remain unused
}
compactStyle = properties.getCompactStyle();
uloc = symbols.getULocale();
@ -337,12 +381,12 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
private static final class CompactDecimalDataSink extends UResource.Sink {
final CompactDecimalData data;
final DecimalFormatSymbols symbols;
final CompactStyle compactStyle;
final CompactType compactType;
final String currencySymbol;
final PNAffixGenerator pnag;
CompactDecimalData data;
DecimalFormatSymbols symbols;
CompactStyle compactStyle;
CompactType compactType;
String currencySymbol;
PNAffixGenerator pnag;
IllegalArgumentException exception;
/*

View file

@ -13,6 +13,7 @@ import com.ibm.icu.impl.number.Rounder;
import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
import com.ibm.icu.impl.number.rounders.IncrementRounder;
import com.ibm.icu.impl.number.rounders.MagnitudeRounder;
import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
import com.ibm.icu.text.CurrencyPluralInfo;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.util.Currency;
@ -288,6 +289,9 @@ public class CurrencyFormat {
};
public static Rounder getCurrencyRounder(DecimalFormatSymbols symbols, IProperties properties) {
if (SignificantDigitsRounder.useSignificantDigits(properties)) {
return SignificantDigitsRounder.getInstance(properties);
}
Properties cprops = threadLocalProperties.get().clear();
populateCurrencyRounderProperties(cprops, symbols, properties);
if (cprops.getRoundingIncrement() != null) {

View file

@ -86,12 +86,13 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
}
public static boolean allowsDecimalPoint(IProperties properties) {
return properties.getDecimalSeparatorAlwaysShown() || properties.getMaximumFractionDigits() != 0;
return properties.getDecimalSeparatorAlwaysShown()
|| properties.getMaximumFractionDigits() != 0;
}
// Properties
private final boolean alwaysShowDecimal;
private final int groupingSize;
private final int primaryGroupingSize;
private final int secondaryGroupingSize;
private final int minimumGroupingDigits;
@ -104,14 +105,10 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
private final int codePointZero;
public PositiveDecimalFormat(DecimalFormatSymbols symbols, IProperties properties) {
groupingSize =
(properties.getGroupingSize() < 0)
? properties.getSecondaryGroupingSize()
: properties.getGroupingSize();
secondaryGroupingSize =
(properties.getSecondaryGroupingSize() < 0)
? properties.getGroupingSize()
: properties.getSecondaryGroupingSize();
int _primary = properties.getGroupingSize();
int _secondary = properties.getSecondaryGroupingSize();
primaryGroupingSize = _primary > 0 ? _primary : _secondary > 0 ? _secondary : 0;
secondaryGroupingSize = _secondary > 0 ? _secondary : primaryGroupingSize;
minimumGroupingDigits = properties.getMinimumGroupingDigits();
alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
@ -167,7 +164,9 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
// Add the decimal point
if (input.getLowerDisplayMagnitude() < 0 || alwaysShowDecimal) {
length += string.insert(startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
length +=
string.insert(
startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
}
// Add the fraction digits
@ -182,12 +181,16 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
int integerCount = input.getUpperDisplayMagnitude() + 1;
for (int i = 0; i < integerCount; i++) {
// Add grouping separator
if (groupingSize > 0 && i == groupingSize && integerCount - i >= minimumGroupingDigits) {
length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
if (primaryGroupingSize > 0
&& i == primaryGroupingSize
&& integerCount - i >= minimumGroupingDigits) {
length +=
string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
} else if (secondaryGroupingSize > 0
&& i > groupingSize
&& (i - groupingSize) % secondaryGroupingSize == 0) {
length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
&& i > primaryGroupingSize
&& (i - primaryGroupingSize) % secondaryGroupingSize == 0) {
length +=
string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
}
// Get and append the next digit value
@ -219,9 +222,13 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
@Override
public void export(Properties properties) {
// For backwards compatibility, export 0 as secondary grouping if primary and secondary are the same
int effectiveSecondaryGroupingSize =
secondaryGroupingSize == primaryGroupingSize ? 0 : secondaryGroupingSize;
properties.setDecimalSeparatorAlwaysShown(alwaysShowDecimal);
properties.setGroupingSize(groupingSize);
properties.setSecondaryGroupingSize(secondaryGroupingSize);
properties.setGroupingSize(primaryGroupingSize);
properties.setSecondaryGroupingSize(effectiveSecondaryGroupingSize);
properties.setMinimumGroupingDigits(minimumGroupingDigits);
}
}

View file

@ -9,19 +9,11 @@
package com.ibm.icu.text;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Locale;
import com.ibm.icu.impl.number.FormatQuantity4;
import com.ibm.icu.impl.number.Properties;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
/**
@ -55,7 +47,7 @@ import com.ibm.icu.util.ULocale;
*/
public class CompactDecimalFormat extends DecimalFormat {
private static final long serialVersionUID = 4716293295276629682L;
private static final long serialVersionUID = 4716293295276629682L;
/**
* Style parameter for CompactDecimalFormat.
@ -122,109 +114,6 @@ public class CompactDecimalFormat extends DecimalFormat {
refreshFormatter();
}
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
FormatQuantity4 fq = new FormatQuantity4(number);
formatter.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
/**
* {@inheritDoc}
*
* @stable ICU 50
*/
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
if (!(obj instanceof Number)) throw new IllegalArgumentException();
Number number = (Number) obj;
FormatQuantity4 fq = new FormatQuantity4(number);
AttributedCharacterIterator result = formatter.formatToCharacterIterator(fq);
return result;
}
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
FormatQuantity4 fq = new FormatQuantity4(number);
formatter.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
FormatQuantity4 fq = new FormatQuantity4(number);
formatter.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
FormatQuantity4 fq = new FormatQuantity4(number);
formatter.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public StringBuffer format(
com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
formatter.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
// /**
// * {@inheritDoc}
// *
// * @internal ICU 57 technology preview
// * @deprecated This API might change or be removed in a future release.
// */
// @Override
// @Deprecated
// public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
// // TODO(sffc)
// throw new UnsupportedOperationException();
// }
/**
* Parsing is currently unsupported, and throws an UnsupportedOperationException.
*
@ -235,13 +124,13 @@ public class CompactDecimalFormat extends DecimalFormat {
throw new UnsupportedOperationException();
}
// DISALLOW Serialization, at least while draft
private void writeObject(ObjectOutputStream out) throws IOException {
throw new NotSerializableException();
}
private void readObject(ObjectInputStream in) throws IOException {
throw new NotSerializableException();
/**
* Parsing is currently unsupported, and throws an UnsupportedOperationException.
*
* @stable ICU 49
*/
@Override
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) {
throw new UnsupportedOperationException();
}
}

View file

@ -418,7 +418,11 @@ public class DecimalFormat extends NumberFormat {
// FORMAT AND PARSE APIS //
//=====================================================================================//
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number);
@ -427,7 +431,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number);
@ -436,7 +444,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number);
@ -445,7 +457,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(
java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
@ -455,7 +471,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
@ -464,7 +484,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
/** @stable ICU 3.6 */
/**
* {@inheritDoc}
*
* @stable ICU 3.6
*/
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
if (!(obj instanceof Number)) throw new IllegalArgumentException();
@ -474,7 +498,7 @@ public class DecimalFormat extends NumberFormat {
return result;
}
protected static final ThreadLocal<Properties> threadLocalCurrencyProperties =
private static final ThreadLocal<Properties> threadLocalCurrencyProperties =
new ThreadLocal<Properties>() {
@Override
protected Properties initialValue() {
@ -482,24 +506,42 @@ public class DecimalFormat extends NumberFormat {
}
};
/**
* {@inheritDoc}
*
* @stable ICU 3.0
*/
@Override
public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
// TODO: This is ugly (although not as ugly as it was in ICU 58).
// Currency should be a free parameter, not in property bag. Fix in ICU 60.
Properties cprops = threadLocalCurrencyProperties.get();
SingularFormat fmt = null;
synchronized (this) {
cprops.copyFrom(properties);
// Use the pre-compiled formatter if possible. Otherwise, copy the properties
// and build our own formatter.
// TODO: Consider using a static format path here.
if (currAmt.getCurrency().equals(properties.getCurrency())) {
fmt = formatter;
} else {
cprops.copyFrom(properties);
}
}
if (fmt == null) {
cprops.setCurrency(currAmt.getCurrency());
fmt = Endpoint.fromBTA(cprops, symbols);
}
cprops.setCurrency(currAmt.getCurrency());
FormatQuantity4 fq = new FormatQuantity4(currAmt.getNumber());
// TODO: Use a static format path here
SingularFormat fmt = Endpoint.fromBTA(cprops, symbols);
fmt.format(fq, toAppendTo, pos);
fq.populateUFieldPosition(pos);
return toAppendTo;
}
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public Number parse(String text, ParsePosition parsePosition) {
// Backwards compatibility: use currency parse mode if this is a currency instance
@ -511,7 +553,11 @@ public class DecimalFormat extends NumberFormat {
return result;
}
/** @stable ICU 49 */
/**
* {@inheritDoc}
*
* @stable ICU 49
*/
@Override
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) {
try {
@ -1922,7 +1968,11 @@ public class DecimalFormat extends NumberFormat {
return properties.equals(other.properties) && symbols.equals(other.symbols);
}
/** @stable ICU 2.0 */
/**
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override
public synchronized int hashCode() {
return properties.hashCode();
@ -1936,9 +1986,26 @@ public class DecimalFormat extends NumberFormat {
}
};
/**
* Returns the default value of toString() with extra DecimalFormat-specific information appended
* to the end of the string. This extra information is intended for debugging purposes, and the
* format is not guaranteed to be stable.
*
* @stable ICU 2.0
*/
@Override
public synchronized String toString() {
return "<DecimalFormat " + symbols.toString() + " " + properties.toString() + ">";
public String toString() {
StringBuilder result = new StringBuilder();
result.append(getClass().getName());
result.append("@");
result.append(Integer.toHexString(hashCode()));
result.append(" { symbols@");
result.append(Integer.toHexString(symbols.hashCode()));
synchronized (this) {
properties.toStringBare(result);
}
result.append(" }");
return result.toString();
}
/**
@ -2027,7 +2094,17 @@ public class DecimalFormat extends NumberFormat {
refreshFormatter();
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static interface PropertySetter {
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public void set(Properties props);
}

View file

@ -1044,7 +1044,8 @@ public abstract class NumberFormat extends UFormat {
// ===== End of factory stuff =====
/**
* Overrides hashCode.
* {@inheritDoc}
*
* @stable ICU 2.0
*/
@Override

View file

@ -418,18 +418,73 @@ public class PluralRules implements Serializable {
*/
@Deprecated
public static enum Operand {
/** The double value of the entire number. */
/**
* The double value of the entire number.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
n,
/** The integer value, with the fraction digits truncated off. */
/**
* The integer value, with the fraction digits truncated off.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
i,
/** All visible fraction digits as an integer, including trailing zeros. */
/**
* All visible fraction digits as an integer, including trailing zeros.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
f,
/** Visible fraction digits, not including trailing zeros. */
/**
* Visible fraction digits as an integer, not including trailing zeros.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
t,
/** Number of visible fraction digits. */
/**
* Number of visible fraction digits.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
v,
/**
* Number of visible fraction digits, not including trailing zeros.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
w,
/* deprecated */
/**
* THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC.
*
* <p>Returns the integer value, but will fail if the number has fraction digits.
* That is, using "j" instead of "i" is like implicitly adding "v is 0".
*
* <p>For example, "j is 3" is equivalent to "i is 3 and v is 0": it matches
* "3" but not "3.1" or "3.0".
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
j;
}
@ -439,8 +494,28 @@ public class PluralRules implements Serializable {
*/
@Deprecated
public static interface IFixedDecimal {
/**
* Returns the value corresponding to the specified operand (n, i, f, t, v, or w).
* If the operand is 'n', returns a double; otherwise, returns an integer.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public double getPluralOperand(Operand operand);
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public boolean isNaN();
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public boolean isInfinite();
}
@ -744,6 +819,8 @@ public class PluralRules implements Serializable {
}
/**
* {@inheritDoc}
*
* @internal
* @deprecated This API is ICU internal only.
*/
@ -751,12 +828,13 @@ public class PluralRules implements Serializable {
@Deprecated
public double getPluralOperand(Operand operand) {
switch(operand) {
default: return source;
case n: return source;
case i: return integerValue;
case f: return decimalDigits;
case t: return decimalDigitsWithoutTrailingZeros;
case v: return visibleDecimalDigitCount;
case w: return visibleDecimalDigitCountWithoutTrailingZeros;
default: return source;
}
}
@ -904,17 +982,25 @@ public class PluralRules implements Serializable {
throw new NotSerializableException();
}
/* (non-Javadoc)
* @see com.ibm.icu.text.PluralRules.IFixedDecimal#isNaN()
/**
* {@inheritDoc}
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
@Override
public boolean isNaN() {
return Double.isNaN(source);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.PluralRules.IFixedDecimal#isInfinite()
/**
* {@inheritDoc}
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
@Override
public boolean isInfinite() {
return Double.isInfinite(source);

View file

@ -762,15 +762,30 @@ public class Currency extends MeasureUnit {
private String isoCode;
private String currencyString;
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public CurrencyStringInfo(String isoCode, String currencyString) {
this.isoCode = isoCode;
this.currencyString = currencyString;
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public String getISOCode() {
return isoCode;
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
@SuppressWarnings("unused")
public String getCurrencyString() {
return currencyString;

View file

@ -850,6 +850,17 @@ parse output breaks
// JDK parses as -1945
(1,945d1) fail K
test parse strict scientific
set locale en
set pattern #E0
set lenient 0
begin
parse output breaks
123 fail JK
123E1 1230
123E0 123
123E fail JK
test parse strict without prefix/suffix
set locale en
set pattern #

View file

@ -553,6 +553,66 @@ public class CompactDecimalFormatTest extends TestFmwk {
}
}
@Test
public void TestLongShortFallback() {
// smn, dz have long but not short
// es_US, es_GT, es_419, ee have short but not long
ULocale[] locs = new ULocale[] {
new ULocale("smn"),
new ULocale("es_US"),
new ULocale("es_GT"),
new ULocale("es_419"),
new ULocale("ee"),
};
double number = 12345.0;
// These expected values are the same in both ICU 58 and 59.
String[][] expectedShortLong = new String[][] {
{ "12K", "12 tuhháát" },
{ "12k", "12 mil" },
{ "12k", "12 mil" },
{ "12k", "12 mil" },
{ "12K", "12K" },
};
for (int i=0; i<locs.length; i++) {
ULocale loc = locs[i];
String expectedShort = expectedShortLong[i][0];
String expectedLong = expectedShortLong[i][1];
CompactDecimalFormat cdfShort = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
CompactDecimalFormat cdfLong = CompactDecimalFormat.getInstance(loc, CompactStyle.LONG);
String actualShort = cdfShort.format(number);
String actualLong = cdfLong.format(number);
assertEquals("Short, locale " + loc, expectedShort, actualShort);
assertEquals("Long, locale " + loc, expectedLong, actualLong);
}
}
@Test
public void TestLocales() {
// Run a CDF over all locales to make sure there are no unexpected exceptions.
ULocale[] locs = ULocale.getAvailableLocales();
for (ULocale loc : locs) {
CompactDecimalFormat cdfShort = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
CompactDecimalFormat cdfLong = CompactDecimalFormat.getInstance(loc, CompactStyle.LONG);
for (double d = 12345.0; d > 0.01; d /= 10) {
String s1 = cdfShort.format(d);
String s2 = cdfLong.format(d);
assertNotNull("Short " + loc, s1);
assertNotNull("Long " + loc, s2);
assertNotEquals("Short " + loc, 0, s1.length());
assertNotEquals("Long " + loc, 0, s2.length());
}
}
}
@Test
public void TestDigitDisplay() {
CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(ULocale.US, CompactStyle.SHORT);
cdf.setMinimumSignificantDigits(2);
String actual = cdf.format(70123.45678);
assertEquals("Should not display any extra fraction digits", "70K", actual);
}
@Test
public void TestBug12422() {
CompactDecimalFormat cdf;

View file

@ -4683,7 +4683,9 @@ public class NumberFormatTest extends TestFmwk {
NumberFormat fmt = NumberFormat.getInstance(new ULocale("en"));
fmt.setMinimumIntegerDigits(10);
FieldPosition pos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
fmt.format(1234, new StringBuffer(), pos);
StringBuffer sb = new StringBuffer();
fmt.format(1234567, sb, pos);
assertEquals("Should have multiple grouping separators", "0,001,234,567", sb.toString());
assertEquals("FieldPosition should report the first occurence", 1, pos.getBeginIndex());
assertEquals("FieldPosition should report the first occurence", 2, pos.getEndIndex());
}
@ -4940,6 +4942,27 @@ public class NumberFormatTest extends TestFmwk {
assertNotEquals("df2 != df1", df2, df1);
}
@Test
public void Test13055() {
DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
df.setMaximumFractionDigits(0);
df.setRoundingMode(BigDecimal.ROUND_HALF_EVEN);
assertEquals("Should round percent toward even number", "216%", df.format(2.155));
}
@Test
public void Test13056() {
DecimalFormat df = new DecimalFormat("#,##0");
assertEquals("Primary grouping should return 3", 3, df.getGroupingSize());
assertEquals("Secondary grouping should return 0", 0, df.getSecondaryGroupingSize());
df.setSecondaryGroupingSize(3);
assertEquals("Primary grouping should still return 3", 3, df.getGroupingSize());
assertEquals("Secondary grouping should still return 0", 0, df.getSecondaryGroupingSize());
df.setGroupingSize(4);
assertEquals("Primary grouping should return 4", 4, df.getGroupingSize());
assertEquals("Secondary should remember explicit setting and return 3", 3, df.getSecondaryGroupingSize());
}
@Test
public void testPercentZero() {
DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
@ -5017,6 +5040,89 @@ public class NumberFormatTest extends TestFmwk {
assertEquals("Rounding mode ordinal from java.math.RoundingMode should be the same", df1, df2);
}
@Test
public void testCurrencySignificantDigits() {
ULocale locale = new ULocale("en-US");
DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
df.setMaximumSignificantDigits(2);
String result = df.format(1234);
assertEquals("Currency rounding should obey significant digits", "$1,200", result);
}
@Test
public void testParseStrictScientific() {
// See ticket #13057
DecimalFormat df = (DecimalFormat) NumberFormat.getScientificInstance();
df.setParseStrict(true);
ParsePosition ppos = new ParsePosition(0);
Number result0 = df.parse("123E4", ppos);
assertEquals("Should accept number with exponent", 1230000L, result0);
assertEquals("Should consume the whole number", 5, ppos.getIndex());
ppos.setIndex(0);
result0 = df.parse("123", ppos);
assertNull("Should reject number without exponent", result0);
ppos.setIndex(0);
CurrencyAmount result1 = df.parseCurrency("USD123", ppos);
assertNull("Should reject currency without exponent", result1);
}
@Test
public void testParseLenientScientific() {
DecimalFormat df = (DecimalFormat) NumberFormat.getScientificInstance();
ParsePosition ppos = new ParsePosition(0);
Number result0 = df.parse("123E", ppos);
assertEquals("Should parse the number in lenient mode", 123L, result0);
assertEquals("Should stop before the E", 3, ppos.getIndex());
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
dfs.setExponentSeparator("EE");
df.setDecimalFormatSymbols(dfs);
ppos.setIndex(0);
result0 = df.parse("123EE", ppos);
assertEquals("Should parse the number in lenient mode", 123L, result0);
assertEquals("Should stop before the EE", 3, ppos.getIndex());
}
@Test
public void testParseAcceptAsciiPercentPermilleFallback() {
ULocale loc = new ULocale("ar");
DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance(loc);
ParsePosition ppos = new ParsePosition(0);
Number result = df.parse("42%", ppos);
assertEquals("Should parse as 0.42 even in ar", new BigDecimal("0.42"), result);
assertEquals("Should consume the entire string even in ar", 3, ppos.getIndex());
// TODO: Is there a better way to make a localized permille formatter?
df.applyPattern(df.toPattern().replace("%", ""));
ppos.setIndex(0);
result = df.parse("42‰", ppos);
assertEquals("Should parse as 0.042 even in ar", new BigDecimal("0.042"), result);
assertEquals("Should consume the entire string even in ar", 3, ppos.getIndex());
}
@Test
public void testParseSubtraction() {
// TODO: Is this a case we need to support? It prevents us from automatically parsing
// minus signs that appear after the number, like in "12-" vs "-12".
DecimalFormat df = new DecimalFormat();
String str = "12 - 5";
ParsePosition ppos = new ParsePosition(0);
Number n1 = df.parse(str, ppos);
Number n2 = df.parse(str, ppos);
assertEquals("Should parse 12 and -5", 7, n1.intValue() + n2.intValue());
}
@Test
public void testMultiCodePointPaddingInPattern() {
DecimalFormat df = new DecimalFormat("a*'நி'###0b");
String result = df.format(12);
assertEquals("Multi-codepoint padding should not be split", "aநிநி12b", result);
df = new DecimalFormat("a*😁###0b");
result = df.format(12);
assertEquals("Single-codepoint padding should not be split", "a😁😁12b", result);
df = new DecimalFormat("a*''###0b");
result = df.format(12);
assertEquals("Quote should be escapable in padding syntax", "a''12b", result);
}
@Test
public void testSignificantDigitsMode() {
String[][] allExpected = {