/* 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;
// 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);
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;
@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) {
@Override
public int getPrefixLength() {
- // FIXME: Localized exponent separator location.
+ // TODO: Localized exponent separator location.
return 0;
}
@Override
public boolean isStrong() {
+ // Scientific is always strong
return true;
}
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;
}
}
}
\ No newline at end of file
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;
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);
+ 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);
+
+ // 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;
+ }
+
+ /** 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();
}
- return data;
- }
-
- 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));
+
+ /** 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;
}
- 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
+
+ // 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;
}
- if (data.isEmpty() && !nsName.equals("latn")) {
- rb.getAllItemsWithFallback("NumberElements/latn", sink);
+
+ public boolean isEmpty() {
+ return isEmpty;
}
- if (sink.exception != null) {
- throw sink.exception;
+
+ @Override
+ public int getMultiplier(int magnitude) {
+ if (magnitude < 0) {
+ return 0;
+ }
+ if (magnitude > largestMagnitude) {
+ magnitude = largestMagnitude;
+ }
+ return multipliers[magnitude];
}
- }
-
- // 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;
+
+ /** Returns the multiplier from the array directly without bounds checking. */
+ public int getMultiplierDirect(int magnitude) {
+ return multipliers[magnitude];
}
- if (magnitude > largestMagnitude) {
- magnitude = largestMagnitude;
+
+ 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;
+ }
}
- 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;
+
+ 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;
}
- multipliers[magnitude] = multiplier;
- isEmpty = false;
- if (magnitude > largestMagnitude) largestMagnitude = magnitude;
- }
-
- public String getPattern(int magnitude, StandardPlural plural) {
- if (magnitude < 0) {
- return null;
+
+ public Set<String> getAllPatterns() {
+ Set<String> result = new HashSet<String>();
+ result.addAll(Arrays.asList(patterns));
+ result.remove(USE_FALLBACK);
+ result.remove(null);
+ return result;
}
- if (magnitude > largestMagnitude) {
- magnitude = largestMagnitude;
+
+ private boolean has(int magnitude, StandardPlural plural) {
+ // Return true if USE_FALLBACK is present
+ return patterns[getIndex(magnitude, plural)] != null;
}
- String patternString = patterns[getIndex(magnitude, plural)];
- if (patternString == null && plural != StandardPlural.OTHER) {
- // Fall back to "other" plural variant
- patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
+
+ private void setPattern(String patternString, int magnitude, StandardPlural plural) {
+ patterns[getIndex(magnitude, plural)] = patternString;
+ isEmpty = false;
+ if (magnitude > largestMagnitude)
+ largestMagnitude = magnitude;
}
- if (patternString == USE_FALLBACK) {
- // Return null if USE_FALLBACK is present
- patternString = null;
+
+ private void setNoFallback(int magnitude, StandardPlural plural) {
+ setPattern(USE_FALLBACK, magnitude, plural);
}
- 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;
+
+ private static final int getIndex(int magnitude, StandardPlural plural) {
+ return magnitude * StandardPlural.COUNT + plural.ordinal();
}
- @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;
+ 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;
- }
-
- // 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
- byte magnitude = (byte) (key.length() - 1);
- byte multiplier = (byte) data.getMultiplierDirect(magnitude);
-
- // Silently ignore divisors that are too big.
- if (magnitude >= CompactData.MAX_DIGITS) continue;
-
- // Iterate over the plural variants ("one", "other", etc)
- UResource.Table pluralVariantsTable = value.getTable();
- for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
-
- // 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;
- }
-
- // 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);
+ @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) {
+
+ // 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;
+
+ // Iterate over the plural variants ("one", "other", etc)
+ UResource.Table pluralVariantsTable = value.getTable();
+ for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
+
+ // 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;
+ }
+
+ // 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);
+ }
+ }
}
- }
- }
- 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;
+ data.setMultiplier(magnitude, multiplier);
+ }
}
- }
}
- }
-
- 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;
- }
}