ICU-13177 Cleaning up Java compact and long names in preparation for C++.
X-SVN-Rev: 40418
This commit is contained in:
parent
33d0dbbb73
commit
c842f7426d
@ -364,6 +364,14 @@ public class ICUResourceBundle extends UResourceBundle {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void getAllItemsWithFallbackNoFail(String path, UResource.Sink sink) {
|
||||
try {
|
||||
getAllItemsWithFallback(path, sink);
|
||||
} catch (MissingResourceException e) {
|
||||
// Quietly ignore the exception.
|
||||
}
|
||||
}
|
||||
|
||||
public void getAllItemsWithFallback(String path, UResource.Sink sink)
|
||||
throws MissingResourceException {
|
||||
// Collect existing and parsed key objects into an array of keys,
|
||||
|
@ -140,7 +140,8 @@ class NumberFormatterImpl {
|
||||
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(innerPattern);
|
||||
|
||||
// Symbols
|
||||
// NOTE: C++ has a special class, SymbolsWrapper, in MacroProps. Java has all the resolution logic here directly.
|
||||
// NOTE: C++ has a special class, SymbolsWrapper, in MacroProps. Java has all the resolution logic here
|
||||
// directly.
|
||||
if (macros.symbols == null) {
|
||||
micros.symbols = DecimalFormatSymbols.getInstance(macros.loc);
|
||||
} else if (macros.symbols instanceof DecimalFormatSymbols) {
|
||||
@ -238,7 +239,9 @@ class NumberFormatterImpl {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(macros.loc);
|
||||
}
|
||||
CompactType compactType = (macros.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
|
||||
CompactType compactType = (macros.unit instanceof Currency && macros.unitWidth != UnitWidth.FULL_NAME)
|
||||
? CompactType.CURRENCY
|
||||
: CompactType.DECIMAL;
|
||||
chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc, compactType, rules,
|
||||
safe ? patternMod : null, chain);
|
||||
}
|
||||
|
@ -58,25 +58,39 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
|
||||
/* package-private */ MicroPropsGenerator withLocaleData(DecimalFormatSymbols symbols, boolean build,
|
||||
MicroPropsGenerator parent) {
|
||||
return new MurkyScientificHandler(symbols, build, parent);
|
||||
return new ScientificHandler(this, symbols, build, parent);
|
||||
}
|
||||
|
||||
private class MurkyScientificHandler implements MicroPropsGenerator, MultiplierProducer, Modifier {
|
||||
// NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and C++.
|
||||
//
|
||||
// During formatting, we need to provide an object with state (the exponent) as the inner modifier.
|
||||
//
|
||||
// In Java, where the priority is put on reducing object creations, the unsafe code path re-uses the
|
||||
// ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25 ScientificModifier
|
||||
// instances. This scheme reduces the number of object creations by 1 in both safe and unsafe.
|
||||
//
|
||||
// In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply populates
|
||||
// the state (the exponent) into that ScientificModifier. There is no difference between safe and unsafe.
|
||||
|
||||
private static class ScientificHandler implements MicroPropsGenerator, MultiplierProducer, Modifier {
|
||||
|
||||
final ScientificNotation notation;
|
||||
final DecimalFormatSymbols symbols;
|
||||
final ImmutableScientificModifier[] precomputedMods;
|
||||
final ScientificModifier[] precomputedMods;
|
||||
final MicroPropsGenerator parent;
|
||||
/* unsafe */ int exponent;
|
||||
|
||||
private MurkyScientificHandler(DecimalFormatSymbols symbols, boolean safe, MicroPropsGenerator parent) {
|
||||
private ScientificHandler(ScientificNotation notation, DecimalFormatSymbols symbols, boolean safe,
|
||||
MicroPropsGenerator parent) {
|
||||
this.notation = notation;
|
||||
this.symbols = symbols;
|
||||
this.parent = parent;
|
||||
|
||||
if (safe) {
|
||||
// Pre-build the modifiers for exponents -12 through 12
|
||||
precomputedMods = new ImmutableScientificModifier[25];
|
||||
precomputedMods = new ScientificModifier[25];
|
||||
for (int i = -12; i <= 12; i++) {
|
||||
precomputedMods[i + 12] = new ImmutableScientificModifier(i);
|
||||
precomputedMods[i + 12] = new ScientificModifier(i, this);
|
||||
}
|
||||
} else {
|
||||
precomputedMods = null;
|
||||
@ -91,9 +105,9 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
// Treat zero as if it had magnitude 0
|
||||
int exponent;
|
||||
if (quantity.isZero()) {
|
||||
if (requireMinInt && micros.rounding instanceof SignificantRounderImpl) {
|
||||
if (notation.requireMinInt && micros.rounding instanceof SignificantRounderImpl) {
|
||||
// Show "00.000E0" on pattern "00.000E0"
|
||||
((SignificantRounderImpl) micros.rounding).apply(quantity, engineeringInterval);
|
||||
((SignificantRounderImpl) micros.rounding).apply(quantity, notation.engineeringInterval);
|
||||
exponent = 0;
|
||||
} else {
|
||||
micros.rounding.apply(quantity);
|
||||
@ -109,7 +123,7 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
micros.modInner = precomputedMods[exponent + 12];
|
||||
} else if (precomputedMods != null) {
|
||||
// Safe code path B
|
||||
micros.modInner = new ImmutableScientificModifier(exponent);
|
||||
micros.modInner = new ScientificModifier(exponent, this);
|
||||
} else {
|
||||
// Unsafe code path: mutates the object and re-uses it as a Modifier!
|
||||
this.exponent = exponent;
|
||||
@ -124,9 +138,9 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
|
||||
@Override
|
||||
public int getMultiplier(int magnitude) {
|
||||
int interval = engineeringInterval;
|
||||
int interval = notation.engineeringInterval;
|
||||
int digitsShown;
|
||||
if (requireMinInt) {
|
||||
if (notation.requireMinInt) {
|
||||
// For patterns like "000.00E0" and ".00E0"
|
||||
digitsShown = interval;
|
||||
} else if (interval <= 1) {
|
||||
@ -141,7 +155,7 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
|
||||
@Override
|
||||
public int getPrefixLength() {
|
||||
// FIXME: Localized exponent separator location.
|
||||
// TODO: Localized exponent separator location.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -153,6 +167,7 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
|
||||
@Override
|
||||
public boolean isStrong() {
|
||||
// Scientific is always strong
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -166,49 +181,52 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
int i = rightIndex;
|
||||
// Append the exponent separator and sign
|
||||
i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL);
|
||||
if (exponent < 0 && exponentSignDisplay != SignDisplay.NEVER) {
|
||||
if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER) {
|
||||
i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
|
||||
} else if (exponentSignDisplay == SignDisplay.ALWAYS) {
|
||||
} else if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
|
||||
i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN);
|
||||
}
|
||||
// Append the exponent digits (using a simple inline algorithm)
|
||||
int disp = Math.abs(exponent);
|
||||
for (int j = 0; j < minExponentDigits || disp > 0; j++, disp /= 10) {
|
||||
for (int j = 0; j < notation.minExponentDigits || disp > 0; j++, disp /= 10) {
|
||||
int d = disp % 10;
|
||||
String digitString = symbols.getDigitStringsLocal()[d];
|
||||
i += output.insert(i - j, digitString, NumberFormat.Field.EXPONENT);
|
||||
}
|
||||
return i - rightIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private class ImmutableScientificModifier implements Modifier {
|
||||
final int exponent;
|
||||
private static class ScientificModifier implements Modifier {
|
||||
final int exponent;
|
||||
final ScientificHandler handler;
|
||||
|
||||
ImmutableScientificModifier(int exponent) {
|
||||
this.exponent = exponent;
|
||||
}
|
||||
ScientificModifier(int exponent, ScientificHandler handler) {
|
||||
this.exponent = exponent;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
return doApply(exponent, output, rightIndex);
|
||||
}
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
return handler.doApply(exponent, output, rightIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrefixLength() {
|
||||
// FIXME: Localized exponent separator location.
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int getPrefixLength() {
|
||||
// TODO: Localized exponent separator location.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCodePointCount() {
|
||||
// This method is not used for strong modifiers.
|
||||
throw new AssertionError();
|
||||
}
|
||||
@Override
|
||||
public int getCodePointCount() {
|
||||
// This method is not used for strong modifiers.
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStrong() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean isStrong() {
|
||||
// Scientific is always strong
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ package newapi.impl;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Set;
|
||||
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
@ -20,257 +19,226 @@ import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class CompactData implements MultiplierProducer {
|
||||
|
||||
public static CompactData getInstance(
|
||||
ULocale locale, CompactType compactType, CompactStyle compactStyle) {
|
||||
// TODO: Add a data cache? It would be keyed by locale, compact type, and compact style.
|
||||
CompactData data = new CompactData();
|
||||
CompactDataSink sink = new CompactDataSink(data, compactType, compactStyle);
|
||||
String nsName = NumberingSystem.getInstance(locale).getName();
|
||||
ICUResourceBundle rb =
|
||||
(ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
CompactData.internalPopulateData(nsName, rb, sink, data);
|
||||
if (data.isEmpty() && compactStyle == CompactStyle.LONG) {
|
||||
// No long data is available; load short data instead
|
||||
sink.compactStyle = CompactStyle.SHORT;
|
||||
CompactData.internalPopulateData(nsName, rb, sink, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
public static CompactData getInstance(ULocale locale, CompactType compactType, CompactStyle compactStyle) {
|
||||
// TODO: Add a data cache? It would be keyed by locale, compact type, and compact style.
|
||||
CompactData data = new CompactData();
|
||||
CompactDataSink sink = new CompactDataSink(data);
|
||||
String nsName = NumberingSystem.getInstance(locale).getName();
|
||||
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
|
||||
public static CompactData getInstance(
|
||||
Map<String, Map<String, String>> powersToPluralsToPatterns) {
|
||||
CompactData data = new CompactData();
|
||||
for (Map.Entry<String, Map<String, String>> magnitudeEntry :
|
||||
powersToPluralsToPatterns.entrySet()) {
|
||||
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
|
||||
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
|
||||
StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
|
||||
String patternString = pluralEntry.getValue().toString();
|
||||
data.setPattern(patternString, magnitude, plural);
|
||||
int numZeros = countZeros(patternString);
|
||||
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
|
||||
data.setMultiplier(magnitude, (byte) (numZeros - magnitude - 1));
|
||||
// Fall back to latn numbering system and/or short compact style.
|
||||
String resourceKey = getResourceBundleKey(nsName, compactStyle, compactType);
|
||||
rb.getAllItemsWithFallbackNoFail(resourceKey, sink);
|
||||
if (data.isEmpty() && !nsName.equals("latn")) {
|
||||
resourceKey = getResourceBundleKey("latn", compactStyle, compactType);
|
||||
rb.getAllItemsWithFallbackNoFail(resourceKey, sink);
|
||||
}
|
||||
}
|
||||
if (data.isEmpty() && compactStyle != CompactStyle.SHORT) {
|
||||
resourceKey = getResourceBundleKey(nsName, CompactStyle.SHORT, compactType);
|
||||
rb.getAllItemsWithFallbackNoFail(resourceKey, sink);
|
||||
}
|
||||
if (data.isEmpty() && !nsName.equals("latn") && compactStyle != CompactStyle.SHORT) {
|
||||
resourceKey = getResourceBundleKey("latn", CompactStyle.SHORT, compactType);
|
||||
rb.getAllItemsWithFallbackNoFail(resourceKey, sink);
|
||||
}
|
||||
|
||||
// The last fallback is guaranteed to return data.
|
||||
assert (!data.isEmpty());
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void internalPopulateData(
|
||||
String nsName, ICUResourceBundle rb, CompactDataSink sink, CompactData data) {
|
||||
try {
|
||||
rb.getAllItemsWithFallback("NumberElements/" + nsName, sink);
|
||||
} catch (MissingResourceException e) {
|
||||
// Fall back to latn
|
||||
/** Returns a string like "NumberElements/latn/patternsShort/decimalFormat". */
|
||||
private static String getResourceBundleKey(String nsName, CompactStyle compactStyle, CompactType compactType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("NumberElements/");
|
||||
sb.append(nsName);
|
||||
sb.append(compactStyle == CompactStyle.SHORT ? "/patternsShort" : "/patternsLong");
|
||||
sb.append(compactType == CompactType.DECIMAL ? "/decimalFormat" : "/currencyFormat");
|
||||
return sb.toString();
|
||||
}
|
||||
if (data.isEmpty() && !nsName.equals("latn")) {
|
||||
rb.getAllItemsWithFallback("NumberElements/latn", sink);
|
||||
|
||||
/** Java-only method used by CLDR tooling. */
|
||||
public static CompactData getInstance(Map<String, Map<String, String>> powersToPluralsToPatterns) {
|
||||
CompactData data = new CompactData();
|
||||
for (Map.Entry<String, Map<String, String>> magnitudeEntry : powersToPluralsToPatterns.entrySet()) {
|
||||
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
|
||||
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
|
||||
StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
|
||||
String patternString = pluralEntry.getValue().toString();
|
||||
data.setPattern(patternString, magnitude, plural);
|
||||
int numZeros = countZeros(patternString);
|
||||
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
|
||||
data.setMultiplier(magnitude, (byte) (numZeros - magnitude - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
if (sink.exception != null) {
|
||||
throw sink.exception;
|
||||
|
||||
// A dummy object used when a "0" compact decimal entry is encountered. This is necessary
|
||||
// in order to prevent falling back to root. Object equality ("==") is intended.
|
||||
private static final String USE_FALLBACK = "<USE FALLBACK>";
|
||||
|
||||
private final String[] patterns;
|
||||
private final byte[] multipliers;
|
||||
private boolean isEmpty;
|
||||
private int largestMagnitude;
|
||||
|
||||
private static final int MAX_DIGITS = 15;
|
||||
|
||||
private CompactData() {
|
||||
patterns = new String[(CompactData.MAX_DIGITS + 1) * StandardPlural.COUNT];
|
||||
multipliers = new byte[CompactData.MAX_DIGITS + 1];
|
||||
isEmpty = true;
|
||||
largestMagnitude = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// A dummy object used when a "0" compact decimal entry is encountered. This is necessary
|
||||
// in order to prevent falling back to root. Object equality ("==") is intended.
|
||||
private static final String USE_FALLBACK = "<USE FALLBACK>";
|
||||
|
||||
private final String[] patterns;
|
||||
private final byte[] multipliers;
|
||||
private boolean isEmpty;
|
||||
private int largestMagnitude;
|
||||
|
||||
private static final int MAX_DIGITS = 15;
|
||||
|
||||
private CompactData() {
|
||||
patterns = new String[(CompactData.MAX_DIGITS + 1) * StandardPlural.COUNT];
|
||||
multipliers = new byte[CompactData.MAX_DIGITS + 1];
|
||||
isEmpty = true;
|
||||
largestMagnitude = 0;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMultiplier(int magnitude) {
|
||||
if (magnitude < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (magnitude > largestMagnitude) {
|
||||
magnitude = largestMagnitude;
|
||||
}
|
||||
return multipliers[magnitude];
|
||||
}
|
||||
|
||||
/** Returns the multiplier from the array directly without bounds checking. */
|
||||
public int getMultiplierDirect(int magnitude) {
|
||||
return multipliers[magnitude];
|
||||
}
|
||||
|
||||
private void setMultiplier(int magnitude, byte multiplier) {
|
||||
if (multipliers[magnitude] != 0) {
|
||||
assert multipliers[magnitude] == multiplier;
|
||||
return;
|
||||
}
|
||||
multipliers[magnitude] = multiplier;
|
||||
isEmpty = false;
|
||||
if (magnitude > largestMagnitude) largestMagnitude = magnitude;
|
||||
}
|
||||
|
||||
public String getPattern(int magnitude, StandardPlural plural) {
|
||||
if (magnitude < 0) {
|
||||
return null;
|
||||
}
|
||||
if (magnitude > largestMagnitude) {
|
||||
magnitude = largestMagnitude;
|
||||
}
|
||||
String patternString = patterns[getIndex(magnitude, plural)];
|
||||
if (patternString == null && plural != StandardPlural.OTHER) {
|
||||
// Fall back to "other" plural variant
|
||||
patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
|
||||
}
|
||||
if (patternString == USE_FALLBACK) {
|
||||
// Return null if USE_FALLBACK is present
|
||||
patternString = null;
|
||||
}
|
||||
return patternString;
|
||||
}
|
||||
|
||||
public Set<String> getAllPatterns() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
result.addAll(Arrays.asList(patterns));
|
||||
result.remove(USE_FALLBACK);
|
||||
result.remove(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean has(int magnitude, StandardPlural plural) {
|
||||
// Return true if USE_FALLBACK is present
|
||||
return patterns[getIndex(magnitude, plural)] != null;
|
||||
}
|
||||
|
||||
private void setPattern(String patternString, int magnitude, StandardPlural plural) {
|
||||
patterns[getIndex(magnitude, plural)] = patternString;
|
||||
isEmpty = false;
|
||||
if (magnitude > largestMagnitude) largestMagnitude = magnitude;
|
||||
}
|
||||
|
||||
private void setNoFallback(int magnitude, StandardPlural plural) {
|
||||
setPattern(USE_FALLBACK, magnitude, plural);
|
||||
}
|
||||
|
||||
private static final int getIndex(int magnitude, StandardPlural plural) {
|
||||
return magnitude * StandardPlural.COUNT + plural.ordinal();
|
||||
}
|
||||
|
||||
private static final class CompactDataSink extends UResource.Sink {
|
||||
|
||||
CompactData data;
|
||||
CompactStyle compactStyle;
|
||||
CompactType compactType;
|
||||
IllegalArgumentException exception;
|
||||
|
||||
/*
|
||||
* NumberElements{ <-- top (numbering system table)
|
||||
* latn{ <-- patternsTable (one per numbering system)
|
||||
* patternsLong{ <-- formatsTable (one per pattern)
|
||||
* decimalFormat{ <-- powersOfTenTable (one per format)
|
||||
* 1000{ <-- pluralVariantsTable (one per power of ten)
|
||||
* one{"0 thousand"} <-- plural variant and template
|
||||
*/
|
||||
|
||||
public CompactDataSink(CompactData data, CompactType compactType, CompactStyle compactStyle) {
|
||||
this.data = data;
|
||||
this.compactType = compactType;
|
||||
this.compactStyle = compactStyle;
|
||||
public boolean isEmpty() {
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
|
||||
UResource.Table patternsTable = value.getTable();
|
||||
for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) {
|
||||
if (key.contentEquals("patternsShort") && compactStyle == CompactStyle.SHORT) {
|
||||
} else if (key.contentEquals("patternsLong") && compactStyle == CompactStyle.LONG) {
|
||||
} else {
|
||||
continue;
|
||||
public int getMultiplier(int magnitude) {
|
||||
if (magnitude < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (magnitude > largestMagnitude) {
|
||||
magnitude = largestMagnitude;
|
||||
}
|
||||
return multipliers[magnitude];
|
||||
}
|
||||
|
||||
/** Returns the multiplier from the array directly without bounds checking. */
|
||||
public int getMultiplierDirect(int magnitude) {
|
||||
return multipliers[magnitude];
|
||||
}
|
||||
|
||||
private void setMultiplier(int magnitude, byte multiplier) {
|
||||
if (multipliers[magnitude] != 0) {
|
||||
assert multipliers[magnitude] == multiplier;
|
||||
return;
|
||||
}
|
||||
multipliers[magnitude] = multiplier;
|
||||
isEmpty = false;
|
||||
if (magnitude > largestMagnitude) {
|
||||
largestMagnitude = magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPattern(int magnitude, StandardPlural plural) {
|
||||
if (magnitude < 0) {
|
||||
return null;
|
||||
}
|
||||
if (magnitude > largestMagnitude) {
|
||||
magnitude = largestMagnitude;
|
||||
}
|
||||
String patternString = patterns[getIndex(magnitude, plural)];
|
||||
if (patternString == null && plural != StandardPlural.OTHER) {
|
||||
// Fall back to "other" plural variant
|
||||
patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
|
||||
}
|
||||
if (patternString == USE_FALLBACK) { // == is intended
|
||||
// Return null if USE_FALLBACK is present
|
||||
patternString = null;
|
||||
}
|
||||
return patternString;
|
||||
}
|
||||
|
||||
public Set<String> getAllPatterns() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
result.addAll(Arrays.asList(patterns));
|
||||
result.remove(USE_FALLBACK);
|
||||
result.remove(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean has(int magnitude, StandardPlural plural) {
|
||||
// Return true if USE_FALLBACK is present
|
||||
return patterns[getIndex(magnitude, plural)] != null;
|
||||
}
|
||||
|
||||
private void setPattern(String patternString, int magnitude, StandardPlural plural) {
|
||||
patterns[getIndex(magnitude, plural)] = patternString;
|
||||
isEmpty = false;
|
||||
if (magnitude > largestMagnitude)
|
||||
largestMagnitude = magnitude;
|
||||
}
|
||||
|
||||
private void setNoFallback(int magnitude, StandardPlural plural) {
|
||||
setPattern(USE_FALLBACK, magnitude, plural);
|
||||
}
|
||||
|
||||
private static final int getIndex(int magnitude, StandardPlural plural) {
|
||||
return magnitude * StandardPlural.COUNT + plural.ordinal();
|
||||
}
|
||||
|
||||
private static final class CompactDataSink extends UResource.Sink {
|
||||
|
||||
CompactData data;
|
||||
|
||||
public CompactDataSink(CompactData data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// traverse into the table of formats
|
||||
UResource.Table formatsTable = value.getTable();
|
||||
for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) {
|
||||
if (key.contentEquals("decimalFormat") && compactType == CompactType.DECIMAL) {
|
||||
} else if (key.contentEquals("currencyFormat") && compactType == CompactType.CURRENCY) {
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
|
||||
// traverse into the table of powers of ten
|
||||
UResource.Table powersOfTenTable = value.getTable();
|
||||
for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
|
||||
|
||||
// traverse into the table of powers of ten
|
||||
UResource.Table powersOfTenTable = value.getTable();
|
||||
for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
|
||||
// Assumes that the keys are always of the form "10000" where the magnitude is the
|
||||
// length of the key minus one. We expect magnitudes to be less than MAX_DIGITS.
|
||||
byte magnitude = (byte) (key.length() - 1);
|
||||
byte multiplier = (byte) data.getMultiplierDirect(magnitude);
|
||||
assert magnitude < MAX_DIGITS;
|
||||
|
||||
// Assumes that the keys are always of the form "10000" where the magnitude is the
|
||||
// length of the key minus one
|
||||
byte magnitude = (byte) (key.length() - 1);
|
||||
byte multiplier = (byte) data.getMultiplierDirect(magnitude);
|
||||
// Iterate over the plural variants ("one", "other", etc)
|
||||
UResource.Table pluralVariantsTable = value.getTable();
|
||||
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
|
||||
|
||||
// Silently ignore divisors that are too big.
|
||||
if (magnitude >= CompactData.MAX_DIGITS) continue;
|
||||
// Skip this magnitude/plural if we already have it from a child locale.
|
||||
StandardPlural plural = StandardPlural.fromString(key.toString());
|
||||
if (data.has(magnitude, plural)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over the plural variants ("one", "other", etc)
|
||||
UResource.Table pluralVariantsTable = value.getTable();
|
||||
for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
|
||||
// The value "0" means that we need to use the default pattern and not fall back
|
||||
// to parent locales. Example locale where this is relevant: 'it'.
|
||||
String patternString = value.toString();
|
||||
if (patternString.equals("0")) {
|
||||
data.setNoFallback(magnitude, plural);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip this magnitude/plural if we already have it from a child locale.
|
||||
StandardPlural plural = StandardPlural.fromString(key.toString());
|
||||
if (data.has(magnitude, plural)) {
|
||||
continue;
|
||||
}
|
||||
// Save the pattern string. We will parse it lazily.
|
||||
data.setPattern(patternString, magnitude, plural);
|
||||
|
||||
// The value "0" means that we need to use the default pattern and not fall back
|
||||
// to parent locales. Example locale where this is relevant: 'it'.
|
||||
String patternString = value.toString();
|
||||
if (patternString.equals("0")) {
|
||||
data.setNoFallback(magnitude, plural);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the pattern string. We will parse it lazily.
|
||||
data.setPattern(patternString, magnitude, plural);
|
||||
|
||||
// If necessary, compute the multiplier: the difference between the magnitude
|
||||
// and the number of zeros in the pattern.
|
||||
if (multiplier == 0) {
|
||||
int numZeros = countZeros(patternString);
|
||||
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
|
||||
multiplier = (byte) (numZeros - magnitude - 1);
|
||||
// If necessary, compute the multiplier: the difference between the magnitude
|
||||
// and the number of zeros in the pattern.
|
||||
if (multiplier == 0) {
|
||||
int numZeros = countZeros(patternString);
|
||||
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
|
||||
multiplier = (byte) (numZeros - magnitude - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.setMultiplier(magnitude, multiplier);
|
||||
}
|
||||
|
||||
data.setMultiplier(magnitude, multiplier);
|
||||
}
|
||||
|
||||
// We want only one table of compact decimal formats, so if we get here, stop consuming.
|
||||
// The data.isEmpty() check will prevent further bundles from being traversed.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int countZeros(String patternString) {
|
||||
// NOTE: This strategy for computing the number of zeros is a hack for efficiency.
|
||||
// It could break if there are any 0s that aren't part of the main pattern.
|
||||
int numZeros = 0;
|
||||
for (int i = 0; i < patternString.length(); i++) {
|
||||
if (patternString.charAt(i) == '0') {
|
||||
numZeros++;
|
||||
} else if (numZeros > 0) {
|
||||
break; // zeros should always be contiguous
|
||||
}
|
||||
private static final int countZeros(String patternString) {
|
||||
// NOTE: This strategy for computing the number of zeros is a hack for efficiency.
|
||||
// It could break if there are any 0s that aren't part of the main pattern.
|
||||
int numZeros = 0;
|
||||
for (int i = 0; i < patternString.length(); i++) {
|
||||
if (patternString.charAt(i) == '0') {
|
||||
numZeros++;
|
||||
} else if (numZeros > 0) {
|
||||
break; // zeros should always be contiguous
|
||||
}
|
||||
}
|
||||
return numZeros;
|
||||
}
|
||||
return numZeros;
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
String pluralKeyword = e.getKey();
|
||||
StandardPlural plural = StandardPlural.fromString(e.getKey());
|
||||
String longName = currency.getName(loc, Currency.PLURAL_LONG_NAME, pluralKeyword, null);
|
||||
String simpleFormat = e.getValue(); // e.g., "{0} {1}"
|
||||
String simpleFormat = e.getValue();
|
||||
// Example pattern from data: "{0} {1}"
|
||||
// Example output after find-and-replace: "{0} US dollars"
|
||||
simpleFormat = simpleFormat.replace("{1}", longName);
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
|
||||
|
@ -186,13 +186,50 @@ public class NumberFormatterTest {
|
||||
"$0.0088",
|
||||
"$0");
|
||||
|
||||
assertFormatDescending(
|
||||
"Compact Short with ISO Currency",
|
||||
"C $USD unit-width=ISO_CODE",
|
||||
NumberFormatter.with()
|
||||
.notation(Notation.compactShort())
|
||||
.unit(USD)
|
||||
.unitWidth(UnitWidth.ISO_CODE),
|
||||
ULocale.ENGLISH,
|
||||
"USD 88K",
|
||||
"USD 8.8K",
|
||||
"USD 876",
|
||||
"USD 88",
|
||||
"USD 8.8",
|
||||
"USD 0.88",
|
||||
"USD 0.088",
|
||||
"USD 0.0088",
|
||||
"USD 0");
|
||||
|
||||
assertFormatDescending(
|
||||
"Compact Short with Long Name Currency",
|
||||
"C $USD unit-width=FULL_NAME",
|
||||
NumberFormatter.with()
|
||||
.notation(Notation.compactShort())
|
||||
.unit(USD)
|
||||
.unitWidth(UnitWidth.FULL_NAME),
|
||||
ULocale.ENGLISH,
|
||||
"88K US dollars",
|
||||
"8.8K US dollars",
|
||||
"876 US dollars",
|
||||
"88 US dollars",
|
||||
"8.8 US dollars",
|
||||
"0.88 US dollars",
|
||||
"0.088 US dollars",
|
||||
"0.0088 US dollars",
|
||||
"0 US dollars");
|
||||
|
||||
// Note: Most locales don't have compact long currency, so this currently falls back to short.
|
||||
// This test case should be fixed when proper compact long currency patterns are added.
|
||||
assertFormatDescending(
|
||||
"Compact Long Currency",
|
||||
"CC $USD",
|
||||
NumberFormatter.with().notation(Notation.compactLong()).unit(USD),
|
||||
ULocale.ENGLISH,
|
||||
"$88K",
|
||||
"$88K", // should be something like "$88 thousand"
|
||||
"$8.8K",
|
||||
"$876",
|
||||
"$88",
|
||||
@ -202,6 +239,45 @@ public class NumberFormatterTest {
|
||||
"$0.0088",
|
||||
"$0");
|
||||
|
||||
// Note: Most locales don't have compact long currency, so this currently falls back to short.
|
||||
// This test case should be fixed when proper compact long currency patterns are added.
|
||||
assertFormatDescending(
|
||||
"Compact Long with ISO Currency",
|
||||
"CC $USD unit-width=ISO_CODE",
|
||||
NumberFormatter.with()
|
||||
.notation(Notation.compactLong())
|
||||
.unit(USD)
|
||||
.unitWidth(UnitWidth.ISO_CODE),
|
||||
ULocale.ENGLISH,
|
||||
"USD 88K", // should be something like "USD 88 thousand"
|
||||
"USD 8.8K",
|
||||
"USD 876",
|
||||
"USD 88",
|
||||
"USD 8.8",
|
||||
"USD 0.88",
|
||||
"USD 0.088",
|
||||
"USD 0.0088",
|
||||
"USD 0");
|
||||
|
||||
// TODO: This behavior could be improved and should be revisited.
|
||||
assertFormatDescending(
|
||||
"Compact Long with Long Name Currency",
|
||||
"CC $USD unit-width=FULL_NAME",
|
||||
NumberFormatter.with()
|
||||
.notation(Notation.compactLong())
|
||||
.unit(USD)
|
||||
.unitWidth(UnitWidth.FULL_NAME),
|
||||
ULocale.ENGLISH,
|
||||
"88 thousand US dollars",
|
||||
"8.8 thousand US dollars",
|
||||
"876 US dollars",
|
||||
"88 US dollars",
|
||||
"8.8 US dollars",
|
||||
"0.88 US dollars",
|
||||
"0.088 US dollars",
|
||||
"0.0088 US dollars",
|
||||
"0 US dollars");
|
||||
|
||||
assertFormatSingle(
|
||||
"Compact Plural One",
|
||||
"CC",
|
||||
|
Loading…
Reference in New Issue
Block a user