ICU-9541 Re-integrate branch changes into trunk.
X-SVN-Rev: 32375
This commit is contained in:
parent
1dff0b56ee
commit
237e167bce
@ -6,6 +6,9 @@
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.impl.ICUCache;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.SimpleCache;
|
||||
@ -19,30 +22,42 @@ import com.ibm.icu.util.UResourceBundle;
|
||||
*/
|
||||
class CompactDecimalDataCache {
|
||||
|
||||
private static final int MAX_DIGITS = 15;
|
||||
private final ICUCache<ULocale, Data> cache = new SimpleCache<ULocale, Data>();
|
||||
static final String OTHER = "other";
|
||||
|
||||
/**
|
||||
* We can specify prefixes or suffixes for values with up to 15 digits,
|
||||
* less than 10^15.
|
||||
*/
|
||||
static final int MAX_DIGITS = 15;
|
||||
|
||||
private final ICUCache<ULocale, DataBundle> cache =
|
||||
new SimpleCache<ULocale, DataBundle>();
|
||||
|
||||
/**
|
||||
* Data contains the compact decimal data for a particular locale. Data consists
|
||||
* of three arrays. The index of each array corresponds to log10 of the number
|
||||
* being formatted, so when formatting 12,345, the 4th index of the arrays should
|
||||
* be used. Divisors contain the number to divide by before doing formatting.
|
||||
* In the case of english, <code>divisors[4]</code> is 1000. So to format
|
||||
* 12,345, divide by 1000 to get 12. prefix and suffix contain the prefix and
|
||||
* suffix to use, for english, <code>suffix[4]</code> is "K" So ultimately,
|
||||
* 12,345 is formatted as 12K.
|
||||
* of one array and two hashmaps. The index of the divisors array as well
|
||||
* as the arrays stored in the values of the two hashmaps correspond
|
||||
* to log10 of the number being formatted, so when formatting 12,345, the 4th
|
||||
* index of the arrays should be used. Divisors contain the number to divide
|
||||
* by before doing formatting. In the case of english, <code>divisors[4]</code>
|
||||
* is 1000. So to format 12,345, divide by 1000 to get 12. Then use
|
||||
* PluralRules with the current locale to figure out which of the 6 plural variants
|
||||
* 12 matches: "zero", "one", "two", "few", "many", or "other." Prefixes and
|
||||
* suffixes are maps whose key is the plural variant and whose values are
|
||||
* arrays of strings with indexes corresponding to log10 of the original number.
|
||||
* these arrays contain the prefix or suffix to use.
|
||||
*
|
||||
* Each array in data is 15 in length, and every index is filled.
|
||||
*
|
||||
* @author Travis Keep
|
||||
* *
|
||||
*
|
||||
*/
|
||||
static class Data {
|
||||
long[] divisors;
|
||||
String[] prefixes;
|
||||
String[] suffixes;
|
||||
Map<String, String[]> prefixes;
|
||||
Map<String, String[]> suffixes;
|
||||
|
||||
Data(long[] divisors, String[] prefixes, String[] suffixes) {
|
||||
Data(long[] divisors, Map<String, String[]> prefixes, Map<String, String[]> suffixes) {
|
||||
this.divisors = divisors;
|
||||
this.prefixes = prefixes;
|
||||
this.suffixes = suffixes;
|
||||
@ -50,10 +65,29 @@ class CompactDecimalDataCache {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data for a particular locale.
|
||||
* DataBundle contains compact decimal data for all the styles in a particular
|
||||
* locale. Currently available styles are short and long.
|
||||
*
|
||||
* @author Travis Keep
|
||||
*/
|
||||
Data get(ULocale locale) {
|
||||
Data result = cache.get(locale);
|
||||
static class DataBundle {
|
||||
Data shortData;
|
||||
Data longData;
|
||||
|
||||
DataBundle(Data shortData, Data longData) {
|
||||
this.shortData = shortData;
|
||||
this.longData = longData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch data for a particular locale. Clients must not modify any part
|
||||
* of the returned data. Portions of returned data may be shared so modifying
|
||||
* it will have unpredictable results.
|
||||
*/
|
||||
DataBundle get(ULocale locale) {
|
||||
DataBundle result = cache.get(locale);
|
||||
if (result == null) {
|
||||
result = load(locale);
|
||||
cache.put(locale, result);
|
||||
@ -61,16 +95,54 @@ class CompactDecimalDataCache {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Data load(ULocale ulocale) {
|
||||
/**
|
||||
* Loads the "patternsShort" and "patternsLong" data for a particular locale.
|
||||
* We assume that "patternsShort" data can be found for any locale. If we can't
|
||||
* find it we throw an exception. However, we allow "patternsLong" data to be
|
||||
* missing for a locale. In this case, we assume that the "patternsLong" data
|
||||
* is identical to the "paternsShort" data.
|
||||
* @param ulocale the locale for which we are loading the data.
|
||||
* @return The returned data, never null.
|
||||
*/
|
||||
private static DataBundle load(ULocale ulocale) {
|
||||
NumberingSystem ns = NumberingSystem.getInstance(ulocale);
|
||||
ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
|
||||
getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale);
|
||||
r = r.getWithFallback("NumberElements/" + ns.getName() + "/patternsShort/decimalFormat");
|
||||
String numberingSystemName = ns.getName();
|
||||
Data shortData = loadWithStyle(r, numberingSystemName, ulocale, "patternsShort", false);
|
||||
Data longData = loadWithStyle(r, numberingSystemName, ulocale, "patternsLong", true);
|
||||
if (longData == null) {
|
||||
longData = shortData;
|
||||
}
|
||||
return new DataBundle(shortData, longData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data
|
||||
* @param r the main resource bundle.
|
||||
* @param numberingSystemName The namespace name.
|
||||
* @param allowNullResult If true, returns null if no data can be found
|
||||
* for particular locale and style. If false, throws a runtime exception
|
||||
* if data cannot be found.
|
||||
* @return The loaded data or possibly null if allowNullResult is true.
|
||||
*/
|
||||
private static Data loadWithStyle(
|
||||
ICUResourceBundle r, String numberingSystemName, ULocale locale, String style,
|
||||
boolean allowNullResult) {
|
||||
String resourcePath =
|
||||
"NumberElements/" + numberingSystemName + "/" + style + "/decimalFormat";
|
||||
if (allowNullResult) {
|
||||
r = r.findWithFallback(resourcePath);
|
||||
} else {
|
||||
r = r.getWithFallback(resourcePath);
|
||||
}
|
||||
int size = r.getSize();
|
||||
Data result = new Data(
|
||||
new long[MAX_DIGITS], new String[MAX_DIGITS], new String[MAX_DIGITS]);
|
||||
new long[MAX_DIGITS],
|
||||
new HashMap<String, String[]>(),
|
||||
new HashMap<String, String[]>());
|
||||
for (int i = 0; i < size; i++) {
|
||||
populateData((ICUResourceBundle) r.get(i), result);
|
||||
populateData(r.get(i), locale, style, result);
|
||||
}
|
||||
fillInMissing(result);
|
||||
return result;
|
||||
@ -78,36 +150,114 @@ class CompactDecimalDataCache {
|
||||
|
||||
/**
|
||||
* Populates Data object with data for a particular divisor from resource bundle.
|
||||
* @param divisorData represents the rules for numbers of a particular size.
|
||||
* This may look like:
|
||||
* <pre>
|
||||
* 10000{
|
||||
* few{"00K"}
|
||||
* many{"00K"}
|
||||
* one{"00 xnb"}
|
||||
* other{"00 xnb"}
|
||||
* }
|
||||
* </pre>
|
||||
* @param locale the locale
|
||||
* @param style the style
|
||||
* @param result rule stored here.
|
||||
*
|
||||
*/
|
||||
private static void populateData(ICUResourceBundle divisorData, Data result) {
|
||||
long divisor = Long.parseLong(divisorData.getKey());
|
||||
int thisIndex = (int) Math.log10(divisor);
|
||||
private static void populateData(
|
||||
UResourceBundle divisorData, ULocale locale, String style, Data result) {
|
||||
// This value will always be some even pwoer of 10. e.g 10000.
|
||||
long magnitude = Long.parseLong(divisorData.getKey());
|
||||
int thisIndex = (int) Math.log10(magnitude);
|
||||
|
||||
// Silently ignore divisors that are too big.
|
||||
if (thisIndex >= MAX_DIGITS) {
|
||||
return;
|
||||
}
|
||||
ICUResourceBundle other = (ICUResourceBundle) divisorData.get("other");
|
||||
int numZeros = populatePrefixSuffix(other.getString(), thisIndex, result);
|
||||
|
||||
int size = divisorData.getSize();
|
||||
|
||||
// keep track of how many zeros are used in the plural variants.
|
||||
// For "00K" this would be 2. This number must be the same for all
|
||||
// plural variants. If they differ, we throw a runtime exception as
|
||||
// such an anomaly is unrecoverable. We expect at least one zero.
|
||||
int numZeros = 0;
|
||||
|
||||
// Keep track if this block defines "other" variant. If a block
|
||||
// fails to define the "other" variant, we must immediately throw
|
||||
// an exception as it is assumed that "other" variants are always
|
||||
// defined.
|
||||
boolean otherVariantDefined = false;
|
||||
|
||||
// Loop over all the plural variants. e.g one, other.
|
||||
for (int i = 0; i < size; i++) {
|
||||
UResourceBundle pluralVariantData = divisorData.get(i);
|
||||
String pluralVariant = pluralVariantData.getKey();
|
||||
String template = pluralVariantData.getString();
|
||||
if (pluralVariant.equals(OTHER)) {
|
||||
otherVariantDefined = true;
|
||||
}
|
||||
int nz = populatePrefixSuffix(
|
||||
pluralVariant, thisIndex, template, locale, style, result);
|
||||
if (nz != numZeros) {
|
||||
if (numZeros != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Plural variant '" + pluralVariant + "' template '" +
|
||||
template + "' for 10^" + thisIndex +
|
||||
" has wrong number of zeros in " + localeAndStyle(locale, style));
|
||||
}
|
||||
numZeros = nz;
|
||||
}
|
||||
}
|
||||
|
||||
if (!otherVariantDefined) {
|
||||
throw new IllegalArgumentException(
|
||||
"No 'other' plural variant defined for 10^" + thisIndex +
|
||||
"in " +localeAndStyle(locale, style));
|
||||
}
|
||||
|
||||
// We craft our divisor such that when we divide by it, we get a
|
||||
// number with the same number of digits as zeros found in the
|
||||
// plural variant templates. If our magnitude is 10000 and we have
|
||||
// two 0's in our plural variants, then we want a divisor of 1000.
|
||||
// Note that if we have 43560 which is of same magnitude as 10000.
|
||||
// When we divide by 1000 we a quotient which rounds to 44 (2 digits)
|
||||
long divisor = magnitude;
|
||||
for (int i = 1; i < numZeros; i++) {
|
||||
divisor /= 10;
|
||||
}
|
||||
result.divisors[thisIndex] = divisor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the prefix and suffix from the template and places them in the
|
||||
* Data object.
|
||||
* @param template the number template, e.g 000K
|
||||
* @param idx the index to store the extracted prefix and suffix
|
||||
* @param result Data object modified in-place here.
|
||||
* Populates prefix and suffix information for a particular plural variant
|
||||
* and index (log10 value).
|
||||
* @param pluralVariant e.g "one", "other"
|
||||
* @param idx the index (log10 value of the number) 0 <= idx < MAX_DIGITS
|
||||
* @param template e.g "00K"
|
||||
* @param locale the locale
|
||||
* @param style the style
|
||||
* @param result Extracted prefix and suffix stored here.
|
||||
* @return number of zeros found before any decimal point in template.
|
||||
*/
|
||||
private static int populatePrefixSuffix(String template, int idx, Data result) {
|
||||
private static int populatePrefixSuffix(
|
||||
String pluralVariant, int idx, String template, ULocale locale, String style,
|
||||
Data result) {
|
||||
int firstIdx = template.indexOf("0");
|
||||
int lastIdx = template.lastIndexOf("0");
|
||||
result.prefixes[idx] = template.substring(0, firstIdx);
|
||||
result.suffixes[idx] = template.substring(lastIdx + 1);
|
||||
|
||||
if (firstIdx == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expect at least one zero in template '" + template +
|
||||
"' for variant '" +pluralVariant + "' for 10^" + idx +
|
||||
" in " + localeAndStyle(locale, style));
|
||||
}
|
||||
savePrefixOrSuffix(
|
||||
template.substring(0, firstIdx), pluralVariant, idx, result.prefixes);
|
||||
savePrefixOrSuffix(
|
||||
template.substring(lastIdx + 1), pluralVariant, idx, result.suffixes);
|
||||
|
||||
// Calculate number of zeros before decimal point.
|
||||
int i = firstIdx + 1;
|
||||
while (i <= lastIdx && template.charAt(i) == '0') {
|
||||
@ -116,33 +266,97 @@ class CompactDecimalDataCache {
|
||||
return i - firstIdx;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns locale and style. Used to form useful messages in thrown
|
||||
* exceptions.
|
||||
* @param locale the locale
|
||||
* @param style the style
|
||||
*/
|
||||
private static String localeAndStyle(ULocale locale, String style) {
|
||||
return "locale '" + locale + "' style '" + style + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* After reading information from resource bundle into a Data object, there
|
||||
* is no guarantee that every index of the arrays will be filled.
|
||||
*
|
||||
* This function walks through the arrays filling in indexes with missing
|
||||
* data from the previous index. If the first indexes are missing data,
|
||||
* they are assumed to have no prefixes or suffixes and a divisor of 1.
|
||||
* We assume an index has missing data if the corresponding element in the
|
||||
* prefixes array is null.
|
||||
* is guarantee that it is complete.
|
||||
*
|
||||
* This method fixes any incomplete data it finds within <code>result</code>.
|
||||
* It looks at each log10 value applying the two rules.
|
||||
* <p>
|
||||
* If no prefix is defined for the "other" variant, use the divisor, prefixes and
|
||||
* suffixes for all defined variants from the previous log10. For log10 = 0,
|
||||
* use all empty prefixes and suffixes and a divisor of 1.
|
||||
* </p><p>
|
||||
* Otherwise, examine each plural variant defined for the given log10 value.
|
||||
* If it has no prefix and suffix for a particular variant, use the one from the
|
||||
* "other" variant.
|
||||
* </p>
|
||||
*
|
||||
* @param result this instance is fixed in-place.
|
||||
*/
|
||||
private static void fillInMissing(Data result) {
|
||||
// Initially we assume that previous divisor is 1 with no prefix or suffix.
|
||||
long lastDivisor = 1L;
|
||||
String lastPrefix = "";
|
||||
String lastSuffix = "";
|
||||
for (int i = 0; i < result.divisors.length; i++) {
|
||||
if (result.prefixes[i] == null) {
|
||||
if (result.prefixes.get(OTHER)[i] == null) {
|
||||
result.divisors[i] = lastDivisor;
|
||||
result.prefixes[i] = lastPrefix;
|
||||
result.suffixes[i] = lastSuffix;
|
||||
copyFromPreviousIndex(i, result.prefixes);
|
||||
copyFromPreviousIndex(i, result.suffixes);
|
||||
} else {
|
||||
lastDivisor = result.divisors[i];
|
||||
lastPrefix = result.prefixes[i];
|
||||
lastSuffix = result.suffixes[i];
|
||||
propagateOtherToMissing(i, result.prefixes);
|
||||
propagateOtherToMissing(i, result.suffixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void propagateOtherToMissing(
|
||||
int idx, Map<String, String[]> prefixesOrSuffixes) {
|
||||
String otherVariantValue = prefixesOrSuffixes.get(OTHER)[idx];
|
||||
for (String[] byBase : prefixesOrSuffixes.values()) {
|
||||
if (byBase[idx] == null) {
|
||||
byBase[idx] = otherVariantValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyFromPreviousIndex(int idx, Map<String, String[]> prefixesOrSuffixes) {
|
||||
for (String[] byBase : prefixesOrSuffixes.values()) {
|
||||
if (idx == 0) {
|
||||
byBase[idx] = "";
|
||||
} else {
|
||||
byBase[idx] = byBase[idx - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void savePrefixOrSuffix(
|
||||
String value, String pluralVariant, int idx,
|
||||
Map<String, String[]> prefixesOrSuffixes) {
|
||||
String[] byBase = prefixesOrSuffixes.get(pluralVariant);
|
||||
if (byBase == null) {
|
||||
byBase = new String[MAX_DIGITS];
|
||||
prefixesOrSuffixes.put(pluralVariant, byBase);
|
||||
}
|
||||
byBase[idx] = value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a prefix or suffix given a plural variant and log10 value. If it
|
||||
* can't find the given variant, it falls back to "other".
|
||||
* @param prefixOrSuffix the prefix or suffix map
|
||||
* @param variant the plural variant
|
||||
* @param base log10 value. 0 <= base < MAX_DIGITS.
|
||||
* @return the prefix or suffix.
|
||||
*/
|
||||
static String getPrefixOrSuffix(
|
||||
Map<String, String[]> prefixOrSuffix, String variant, int base) {
|
||||
String[] byBase = prefixOrSuffix.get(variant);
|
||||
if (byBase == null) {
|
||||
byBase = prefixOrSuffix.get(CompactDecimalDataCache.OTHER);
|
||||
}
|
||||
return byBase[base];
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.text.CompactDecimalDataCache.Data;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
@ -45,17 +46,20 @@ import com.ibm.icu.util.ULocale;
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public class CompactDecimalFormat extends DecimalFormat {
|
||||
|
||||
private static final long serialVersionUID = 4716293295276629682L;
|
||||
|
||||
private static final int MINIMUM_ARRAY_LENGTH = 15;
|
||||
private static final int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_SIZE = 2;
|
||||
private static final CompactDecimalDataCache cache = new CompactDecimalDataCache();
|
||||
|
||||
private final String[] prefix;
|
||||
private final String[] suffix;
|
||||
private final Map<String, String[]> prefix;
|
||||
private final Map<String, String[]> suffix;
|
||||
private final long[] divisor;
|
||||
private final String[] currencyAffixes;
|
||||
|
||||
// null if created internally using explicit prefixes and suffixes.
|
||||
private final PluralRules pluralRules;
|
||||
|
||||
/**
|
||||
* The public mechanism is NumberFormat.getCompactDecimalInstance().
|
||||
*
|
||||
@ -66,16 +70,16 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
*/
|
||||
CompactDecimalFormat(ULocale locale, CompactStyle style) {
|
||||
DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale);
|
||||
CompactDecimalDataCache.Data data = cache.get(locale);
|
||||
CompactDecimalDataCache.Data data = getData(locale, style);
|
||||
this.prefix = data.prefixes;
|
||||
this.suffix = data.suffixes;
|
||||
this.divisor = data.divisors;
|
||||
// TODO fix to consider plural form when choosing a prefix or suffix.
|
||||
applyPattern(format.toPattern());
|
||||
setDecimalFormatSymbols(format.getDecimalFormatSymbols());
|
||||
setMaximumSignificantDigits(2); // default significant digits
|
||||
setSignificantDigitsUsed(true);
|
||||
setGroupingUsed(false);
|
||||
this.pluralRules = PluralRules.forLocale(locale);
|
||||
|
||||
DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
|
||||
currencyAffixes = new String[AFFIX_SIZE];
|
||||
@ -108,8 +112,8 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
*/
|
||||
public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbols, String[] prefix, String[] suffix,
|
||||
long[] divisor, Collection<String> debugCreationErrors, CompactStyle style, String[] currencyAffixes) {
|
||||
if (prefix.length < MINIMUM_ARRAY_LENGTH) {
|
||||
recordError(debugCreationErrors, "Must have at least " + MINIMUM_ARRAY_LENGTH + " prefix items.");
|
||||
if (prefix.length < CompactDecimalDataCache.MAX_DIGITS) {
|
||||
recordError(debugCreationErrors, "Must have at least " + CompactDecimalDataCache.MAX_DIGITS + " prefix items.");
|
||||
}
|
||||
if (prefix.length != suffix.length || prefix.length != divisor.length) {
|
||||
recordError(debugCreationErrors, "Prefix, suffix, and divisor arrays must have the same length.");
|
||||
@ -148,8 +152,8 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
oldDivisor = divisor[i];
|
||||
}
|
||||
|
||||
this.prefix = prefix.clone();
|
||||
this.suffix = suffix.clone();
|
||||
this.prefix = otherPluralVariant(prefix);
|
||||
this.suffix = otherPluralVariant(suffix);
|
||||
this.divisor = divisor.clone();
|
||||
applyPattern(pattern);
|
||||
setDecimalFormatSymbols(formatSymbols);
|
||||
@ -157,6 +161,7 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
setSignificantDigitsUsed(true);
|
||||
setGroupingUsed(false);
|
||||
this.currencyAffixes = currencyAffixes.clone();
|
||||
this.pluralRules = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,12 +174,19 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
if (number < 0.0d) {
|
||||
throw new UnsupportedOperationException("CompactDecimalFormat doesn't handle negative numbers yet.");
|
||||
}
|
||||
int integerCount = number <= 1.0d ? 0 : (int) Math.log10(number);
|
||||
int base = integerCount > 14 ? 14 : integerCount;
|
||||
// We do this here so that the prefix or suffix we choose is always consistent
|
||||
// with the rounding we do. This way, 999999 -> 1M instead of 1000K.
|
||||
number = adjustNumberAsInFormatting(number);
|
||||
int base = number <= 1.0d ? 0 : (int) Math.log10(number);
|
||||
if (base >= CompactDecimalDataCache.MAX_DIGITS) {
|
||||
base = CompactDecimalDataCache.MAX_DIGITS - 1;
|
||||
}
|
||||
number = number / divisor[base];
|
||||
setPositivePrefix(prefix[base]);
|
||||
setPositiveSuffix(suffix[base]);
|
||||
String pluralVariant = getPluralForm(number);
|
||||
setPositivePrefix(CompactDecimalDataCache.getPrefixOrSuffix(prefix, pluralVariant, base));
|
||||
setPositiveSuffix(CompactDecimalDataCache.getPrefixOrSuffix(suffix, pluralVariant, base));
|
||||
setCurrency(null);
|
||||
|
||||
return super.format(number, toAppendTo, pos);
|
||||
}
|
||||
|
||||
@ -248,4 +260,37 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
}
|
||||
creationErrors.add(errorMessage);
|
||||
}
|
||||
|
||||
private Map<String, String[]> otherPluralVariant(String[] prefixOrSuffix) {
|
||||
Map<String, String[]> result = new HashMap<String, String[]>();
|
||||
result.put(CompactDecimalDataCache.OTHER, prefixOrSuffix.clone());
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getPluralForm(double number) {
|
||||
if (pluralRules == null) {
|
||||
return CompactDecimalDataCache.OTHER;
|
||||
}
|
||||
return pluralRules.select(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data for a particular locale and style. If style is unrecognized,
|
||||
* we just return data for CompactStyle.SHORT.
|
||||
* @param locale The locale.
|
||||
* @param style The style.
|
||||
* @return The data which must not be modified.
|
||||
*/
|
||||
private Data getData(ULocale locale, CompactStyle style) {
|
||||
CompactDecimalDataCache.DataBundle bundle = cache.get(locale);
|
||||
switch (style) {
|
||||
case SHORT:
|
||||
return bundle.shortData;
|
||||
case LONG:
|
||||
return bundle.longData;
|
||||
default:
|
||||
return bundle.shortData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -770,10 +770,52 @@ public class DecimalFormat extends NumberFormat {
|
||||
* {@inheritDoc}
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
|
||||
return format(number, result, fieldPosition, false);
|
||||
}
|
||||
|
||||
// See if number is negative.
|
||||
// usage: isNegative(multiply(numberToBeFormatted));
|
||||
private boolean isNegative(double number) {
|
||||
// Detecting whether a double is negative is easy with the exception of the value
|
||||
// -0.0. This is a double which has a zero mantissa (and exponent), but a negative
|
||||
// sign bit. It is semantically distinct from a zero with a positive sign bit, and
|
||||
// this distinction is important to certain kinds of computations. However, it's a
|
||||
// little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
|
||||
// may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
|
||||
// -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
|
||||
// bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
|
||||
return (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
|
||||
}
|
||||
|
||||
// Rounds the number and strips of the negative sign.
|
||||
// usage: round(multiply(numberToBeFormatted))
|
||||
private double round(double number) {
|
||||
boolean isNegative = isNegative(number);
|
||||
if (isNegative)
|
||||
number = -number;
|
||||
|
||||
// Apply rounding after multiplier
|
||||
if (roundingDouble > 0.0) {
|
||||
// number = roundingDouble
|
||||
// * round(number / roundingDouble, roundingMode, isNegative);
|
||||
return round(
|
||||
number, roundingDouble, roundingDoubleReciprocal, roundingMode,
|
||||
isNegative);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
// Multiplies given number by multipler (if there is one) returning the new
|
||||
// number. If there is no multiplier, returns the number passed in unchanged.
|
||||
private double multiply(double number) {
|
||||
if (multiplier != 1) {
|
||||
return number * multiplier;
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
// [Spark/CDL] The actual method to format number. If boolean value
|
||||
// parseAttr == true, then attribute information will be recorded.
|
||||
private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition,
|
||||
@ -805,31 +847,11 @@ public class DecimalFormat extends NumberFormat {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Do this BEFORE checking to see if value is infinite or negative!
|
||||
if (multiplier != 1)
|
||||
number *= multiplier;
|
||||
|
||||
|
||||
// Detecting whether a double is negative is easy with the exception of the value
|
||||
// -0.0. This is a double which has a zero mantissa (and exponent), but a negative
|
||||
// sign bit. It is semantically distinct from a zero with a positive sign bit, and
|
||||
// this distinction is important to certain kinds of computations. However, it's a
|
||||
// little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
|
||||
// may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
|
||||
// -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
|
||||
// bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
|
||||
boolean isNegative = (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
|
||||
if (isNegative)
|
||||
number = -number;
|
||||
|
||||
// Apply rounding after multiplier
|
||||
if (roundingDouble > 0.0) {
|
||||
// number = roundingDouble
|
||||
// * round(number / roundingDouble, roundingMode, isNegative);
|
||||
double newNumber = round(number, roundingDouble, roundingDoubleReciprocal, roundingMode,
|
||||
isNegative);
|
||||
number = newNumber;
|
||||
}
|
||||
// Do this BEFORE checking to see if value is negative or infinite and
|
||||
// before rounding.
|
||||
number = multiply(number);
|
||||
boolean isNegative = isNegative(number);
|
||||
number = round(number);
|
||||
|
||||
if (Double.isInfinite(number)) {
|
||||
int prefixLen = appendAffix(result, isNegative, true, parseAttr);
|
||||
@ -867,6 +889,32 @@ public class DecimalFormat extends NumberFormat {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a special function used by the CompactDecimalFormat subclass.
|
||||
* It completes only the rounding portion of the formatting and returns
|
||||
* the resulting double. CompactDecimalFormat uses the result to compute
|
||||
* the plural form to use.
|
||||
*
|
||||
* @param number The number to format.
|
||||
* @return The number rounded to the correct number of significant digits
|
||||
* with negative sign stripped off.
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
double adjustNumberAsInFormatting(double number) {
|
||||
if (Double.isNaN(number)) {
|
||||
return number;
|
||||
}
|
||||
number = round(multiply(number));
|
||||
if (Double.isInfinite(number)) {
|
||||
return number;
|
||||
}
|
||||
DigitList dl = new DigitList();
|
||||
dl.set(number, precision(false), false);
|
||||
return dl.getDouble();
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a double value to the nearest multiple of the given rounding increment,
|
||||
* according to the given mode. This is equivalent to rounding value/roundingInc to
|
||||
@ -966,6 +1014,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
// [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean
|
||||
@Override
|
||||
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
|
||||
return format(number, result, fieldPosition, false);
|
||||
}
|
||||
@ -1020,6 +1069,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
*
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(BigInteger number, StringBuffer result,
|
||||
FieldPosition fieldPosition) {
|
||||
return format(number, result, fieldPosition, false);
|
||||
@ -1051,6 +1101,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
*
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
|
||||
FieldPosition fieldPosition) {
|
||||
return format(number, result, fieldPosition, false);
|
||||
@ -1080,6 +1131,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
*
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(BigDecimal number, StringBuffer result,
|
||||
FieldPosition fieldPosition) {
|
||||
// This method is just a copy of the corresponding java.math.BigDecimal method
|
||||
@ -1211,7 +1263,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
|
||||
int i;
|
||||
char [] digits = symbols.getDigitsLocal();
|
||||
|
||||
|
||||
char grouping = currencySignCount > 0 ? symbols.getMonetaryGroupingSeparator() :
|
||||
symbols.getGroupingSeparator();
|
||||
char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() :
|
||||
@ -1638,6 +1690,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* <code>null</code> if the parse failed
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public Number parse(String text, ParsePosition parsePosition) {
|
||||
return (Number) parse(text, parsePosition, null);
|
||||
}
|
||||
@ -1657,6 +1710,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @draft ICU 49
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@Override
|
||||
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
|
||||
Currency[] currency = new Currency[1];
|
||||
return (CurrencyAmount) parse(text.toString(), pos, currency);
|
||||
@ -2180,7 +2234,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (digit == 0) {
|
||||
@ -2457,7 +2511,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Utility method used to count the number of codepoints
|
||||
// Utility method used to count the number of codepoints
|
||||
private int countCodePoints(String str,int start, int end) {
|
||||
int count = 0;
|
||||
int index = start;
|
||||
@ -3069,6 +3123,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @see java.math.BigDecimal
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public int getRoundingMode() {
|
||||
return roundingMode;
|
||||
}
|
||||
@ -3086,6 +3141,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @see java.math.BigDecimal
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setRoundingMode(int roundingMode) {
|
||||
if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) {
|
||||
throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode);
|
||||
@ -3094,7 +3150,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
this.roundingMode = roundingMode;
|
||||
|
||||
if (getRoundingIncrement() == null) {
|
||||
setRoundingIncrement(Math.pow(10.0, (double) -getMaximumFractionDigits()));
|
||||
setRoundingIncrement(Math.pow(10.0, -getMaximumFractionDigits()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3501,6 +3557,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* Overrides clone.
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
DecimalFormat other = (DecimalFormat) super.clone();
|
||||
@ -3524,6 +3581,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* Overrides equals.
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
@ -3604,6 +3662,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* Overrides hashCode.
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode() * 37 + positivePrefix.hashCode();
|
||||
// just enough fields for a reasonable distribution
|
||||
@ -3896,6 +3955,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
*
|
||||
* @stable ICU 3.6
|
||||
*/
|
||||
@Override
|
||||
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
|
||||
if (!(obj instanceof Number))
|
||||
throw new IllegalArgumentException();
|
||||
@ -4742,6 +4802,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @see NumberFormat#setMaximumIntegerDigits
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setMaximumIntegerDigits(int newValue) {
|
||||
super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
|
||||
}
|
||||
@ -4753,6 +4814,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @see NumberFormat#setMinimumIntegerDigits
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setMinimumIntegerDigits(int newValue) {
|
||||
super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
|
||||
}
|
||||
@ -4853,6 +4915,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @param theCurrency new currency object to use. Must not be null.
|
||||
* @stable ICU 2.2
|
||||
*/
|
||||
@Override
|
||||
public void setCurrency(Currency theCurrency) {
|
||||
// If we are a currency format, then modify our affixes to
|
||||
// encode the currency symbol for the given currency in our
|
||||
@ -4890,6 +4953,8 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
protected Currency getEffectiveCurrency() {
|
||||
Currency c = getCurrency();
|
||||
if (c == null) {
|
||||
@ -4905,6 +4970,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @see NumberFormat#setMaximumFractionDigits
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setMaximumFractionDigits(int newValue) {
|
||||
super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
|
||||
}
|
||||
@ -4916,6 +4982,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
* @see NumberFormat#setMinimumFractionDigits
|
||||
* @stable ICU 2.0
|
||||
*/
|
||||
@Override
|
||||
public void setMinimumFractionDigits(int newValue) {
|
||||
super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
|
||||
}
|
||||
@ -5514,7 +5581,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
private String posPrefixPatternForCurrency = null;
|
||||
// positive suffix pattern
|
||||
private String posSuffixPatternForCurrency = null;
|
||||
private int patternType;
|
||||
private final int patternType;
|
||||
|
||||
public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix,
|
||||
String posSuffix, int type) {
|
||||
|
@ -37,35 +37,57 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{123456789012345f, "120T"},
|
||||
{12345678901234567890f, "12000000T"},
|
||||
};
|
||||
|
||||
Object[][] SerbianTestData = {
|
||||
{1234f, "1200"},
|
||||
{12345f, "12\u00a0\u0445\u0438\u0459"},
|
||||
{123456f, "120\u00a0\u0445\u0438\u0459"},
|
||||
{1234567f, "1,2\u00a0\u043c\u0438\u043b"},
|
||||
{12345678f, "12\u00a0\u043c\u0438\u043b"},
|
||||
{123456789f, "120\u00a0\u043c\u0438\u043b"},
|
||||
{1234567890f, "1,2\u00a0\u043c\u043b\u0440\u0434"},
|
||||
{12345678901f, "12\u00a0\u043c\u043b\u0440\u0434"},
|
||||
{123456789012f, "120\u00a0\u043c\u043b\u0440\u0434"},
|
||||
{1234567890123f, "1,2\u00a0\u0431\u0438\u043b"},
|
||||
{12345678901234f, "12\u00a0\u0431\u0438\u043b"},
|
||||
{123456789012345f, "120\u00a0\u0431\u0438\u043b"},
|
||||
|
||||
Object[][] SerbianTestDataShort = {
|
||||
{1234, "1200"},
|
||||
{12345, "12K"},
|
||||
{20789, "21\u00a0хиљ"},
|
||||
{123456, "120\u00a0хиљ"},
|
||||
{1234567, "1,2\u00a0мил"},
|
||||
{12345678, "12\u00a0мил"},
|
||||
{123456789, "120\u00a0мил"},
|
||||
{1234567890, "1,2\u00a0млрд"},
|
||||
{12345678901f, "12\u00a0млрд"},
|
||||
{123456789012f, "120\u00a0млрд"},
|
||||
{1234567890123f, "1,2\u00a0бил"},
|
||||
{12345678901234f, "12\u00a0бил"},
|
||||
{123456789012345f, "120\u00a0бил"},
|
||||
{1234567890123456f, "1200\u00a0бил"},
|
||||
};
|
||||
|
||||
Object[][] JapaneseTestData = {
|
||||
{1234f, "1.2\u5343"},
|
||||
{12345f, "1.2\u4E07"},
|
||||
{123456f, "12\u4E07"},
|
||||
{1234567f, "120\u4E07"},
|
||||
{12345678f, "1200\u4E07"},
|
||||
{123456789f, "1.2\u5104"},
|
||||
{1234567890f, "12\u5104"},
|
||||
{12345678901f, "120\u5104"},
|
||||
{123456789012f, "1200\u5104"},
|
||||
{1234567890123f, "1.2\u5146"},
|
||||
{12345678901234f, "12\u5146"},
|
||||
{123456789012345f, "120\u5146"},
|
||||
Object[][] SerbianTestDataLong = {
|
||||
{1234, "1,2 хиљада"},
|
||||
{12345, "12 хиљада"},
|
||||
{21789, "22 хиљаде"},
|
||||
{123456, "120 хиљада"},
|
||||
{999999, "1 милион"},
|
||||
{1234567, "1,2 милиона"},
|
||||
{12345678, "12 милиона"},
|
||||
{123456789, "120 милиона"},
|
||||
{1234567890, "1,2 милијарди"},
|
||||
{12345678901f, "12 милијарди"},
|
||||
{20890123456f, "21 милијарда"},
|
||||
{21890123456f, "22 милијарде"},
|
||||
{123456789012f, "120 милијарди"},
|
||||
{1234567890123f, "1,2 трилиона"},
|
||||
{12345678901234f, "12 трилиона"},
|
||||
{123456789012345f, "120 трилиона"},
|
||||
{1234567890123456f, "1200 трилиона"},
|
||||
};
|
||||
|
||||
Object[][] JapaneseTestData = {
|
||||
{1234f, "1.2千"},
|
||||
{12345f, "1.2万"},
|
||||
{123456f, "12万"},
|
||||
{1234567f, "120万"},
|
||||
{12345678f, "1200万"},
|
||||
{123456789f, "1.2億"},
|
||||
{1234567890f, "12億"},
|
||||
{12345678901f, "120億"},
|
||||
{123456789012f, "1200億"},
|
||||
{1234567890123f, "1.2兆"},
|
||||
{12345678901234f, "12兆"},
|
||||
{123456789012345f, "120兆"},
|
||||
};
|
||||
|
||||
Object[][] SwahiliTestData = {
|
||||
@ -83,29 +105,28 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{12345678901234567890f, "T12000000"},
|
||||
};
|
||||
|
||||
public void TestEnglish() {
|
||||
checkLocale(ULocale.ENGLISH, EnglishTestData);
|
||||
}
|
||||
|
||||
public void TestSerbian() {
|
||||
checkLocale(ULocale.forLanguageTag("sr"), SerbianTestData);
|
||||
public void TestEnglishShort() {
|
||||
checkLocale(ULocale.ENGLISH, CompactStyle.SHORT, EnglishTestData);
|
||||
}
|
||||
|
||||
public void TestJapanese() {
|
||||
checkLocale(ULocale.JAPANESE, JapaneseTestData);
|
||||
public void TestSerbianShort() {
|
||||
checkLocale(ULocale.forLanguageTag("sr"), CompactStyle.SHORT, SerbianTestDataShort);
|
||||
}
|
||||
|
||||
public void TestJapaneseGermany() {
|
||||
// check fallback.
|
||||
checkLocale(ULocale.forLanguageTag("ja-DE"), JapaneseTestData);
|
||||
public void TestSerbianLong() {
|
||||
checkLocale(ULocale.forLanguageTag("sr"), CompactStyle.LONG, SerbianTestDataLong);
|
||||
}
|
||||
|
||||
public void TestSwahili() {
|
||||
checkLocale(ULocale.forLanguageTag("sw"), SwahiliTestData);
|
||||
public void TestJapaneseShort() {
|
||||
checkLocale(ULocale.JAPANESE, CompactStyle.SHORT, JapaneseTestData);
|
||||
}
|
||||
|
||||
public void checkLocale(ULocale locale, Object[][] testData) {
|
||||
CompactDecimalFormat cdf = NumberFormat.getCompactDecimalInstance(locale, CompactStyle.SHORT);
|
||||
public void TestSwahiliShort() {
|
||||
checkLocale(ULocale.forLanguageTag("sw"), CompactStyle.SHORT, SwahiliTestData);
|
||||
}
|
||||
|
||||
public void checkLocale(ULocale locale, CompactStyle style, Object[][] testData) {
|
||||
CompactDecimalFormat cdf = NumberFormat.getCompactDecimalInstance(locale, style);
|
||||
for (Object[] row : testData) {
|
||||
assertEquals(locale + " (" + locale.getDisplayName(locale) + ")", row[1], cdf.format(row[0]));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user