Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(211)

Unified Diff: icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java

Issue 315420043: DecimalFormat rewrite, Java Base URL: svn+icussh://source.icu-project.org/repos/icu/branches/shane/numberformat/
Patch Set: r39857: Last commit before merge to trunk. Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java
===================================================================
--- icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java (revision 39589)
+++ icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java (working copy)
@@ -1,524 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-/*
- *******************************************************************************
- * Copyright (C) 2012-2016, International Business Machines Corporation and
- * others. All Rights Reserved.
- *******************************************************************************
- */
-package com.ibm.icu.text;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.MissingResourceException;
-
-import com.ibm.icu.impl.ICUCache;
-import com.ibm.icu.impl.ICUData;
-import com.ibm.icu.impl.ICUResourceBundle;
-import com.ibm.icu.impl.SimpleCache;
-import com.ibm.icu.impl.UResource;
-import com.ibm.icu.text.DecimalFormat.Unit;
-import com.ibm.icu.util.ULocale;
-import com.ibm.icu.util.UResourceBundle;
-
-/**
- * A cache containing data by locale for {@link CompactDecimalFormat}
- *
- * @author Travis Keep
- */
-class CompactDecimalDataCache {
-
- private static final String SHORT_STYLE = "short";
- private static final String LONG_STYLE = "long";
- private static final String SHORT_CURRENCY_STYLE = "shortCurrency";
- private static final String NUMBER_ELEMENTS = "NumberElements";
- private static final String PATTERNS_LONG = "patternsLong";
- private static final String PATTERNS_SHORT = "patternsShort";
- private static final String DECIMAL_FORMAT = "decimalFormat";
- private static final String CURRENCY_FORMAT = "currencyFormat";
- private static final String LATIN_NUMBERING_SYSTEM = "latn";
-
- private static enum PatternsTableKey { PATTERNS_LONG, PATTERNS_SHORT };
- private static enum FormatsTableKey { DECIMAL_FORMAT, CURRENCY_FORMAT };
-
- public 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 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;
- Map<String, DecimalFormat.Unit[]> units;
- boolean fromFallback;
-
- Data(long[] divisors, Map<String, DecimalFormat.Unit[]> units)
- {
- this.divisors = divisors;
- this.units = units;
- }
-
- public boolean isEmpty() {
- return units == null || units.isEmpty();
- }
- }
-
- /**
- * DataBundle contains compact decimal data for all the styles in a particular
- * locale. Currently available styles are short and long for decimals, and
- * short only for currencies.
- *
- * @author Travis Keep
- */
- static class DataBundle {
- Data shortData;
- Data longData;
- Data shortCurrencyData;
-
- private DataBundle(Data shortData, Data longData, Data shortCurrencyData) {
- this.shortData = shortData;
- this.longData = longData;
- this.shortCurrencyData = shortCurrencyData;
- }
-
- private static DataBundle createEmpty() {
- return new DataBundle(
- new Data(new long[MAX_DIGITS], new HashMap<String, DecimalFormat.Unit[]>()),
- new Data(new long[MAX_DIGITS], new HashMap<String, DecimalFormat.Unit[]>()),
- new Data(new long[MAX_DIGITS], new HashMap<String, DecimalFormat.Unit[]>())
- );
- }
- }
-
- /**
- * Sink for enumerating all of the compact decimal format patterns.
- *
- * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
- * Only store a value if it is still missing, that is, it has not been overridden.
- */
- private static final class CompactDecimalDataSink extends UResource.Sink {
-
- private DataBundle dataBundle; // Where to save values when they are read
- private ULocale locale; // The locale we are traversing (for exception messages)
- private boolean isLatin; // Whether or not we are traversing the Latin table
- private boolean isFallback; // Whether or not we are traversing the Latin table as fallback
-
- /*
- * 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 CompactDecimalDataSink(DataBundle dataBundle, ULocale locale) {
- this.dataBundle = dataBundle;
- this.locale = locale;
- }
-
- @Override
- public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
- // SPECIAL CASE: Don't consume root in the non-Latin numbering system
- if (isRoot && !isLatin) { return; }
-
- UResource.Table patternsTable = value.getTable();
- for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) {
-
- // patterns table: check for patternsShort or patternsLong
- PatternsTableKey patternsTableKey;
- if (key.contentEquals(PATTERNS_SHORT)) {
- patternsTableKey = PatternsTableKey.PATTERNS_SHORT;
- } else if (key.contentEquals(PATTERNS_LONG)) {
- patternsTableKey = PatternsTableKey.PATTERNS_LONG;
- } else {
- continue;
- }
-
- // traverse into the table of formats
- UResource.Table formatsTable = value.getTable();
- for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) {
-
- // formats table: check for decimalFormat or currencyFormat
- FormatsTableKey formatsTableKey;
- if (key.contentEquals(DECIMAL_FORMAT)) {
- formatsTableKey = FormatsTableKey.DECIMAL_FORMAT;
- } else if (key.contentEquals(CURRENCY_FORMAT)) {
- formatsTableKey = FormatsTableKey.CURRENCY_FORMAT;
- } else {
- continue;
- }
-
- // Set the current style and destination based on the lvl1 and lvl2 keys
- String style = null;
- Data destination = null;
- if (patternsTableKey == PatternsTableKey.PATTERNS_LONG
- && formatsTableKey == FormatsTableKey.DECIMAL_FORMAT) {
- style = LONG_STYLE;
- destination = dataBundle.longData;
- } else if (patternsTableKey == PatternsTableKey.PATTERNS_SHORT
- && formatsTableKey == FormatsTableKey.DECIMAL_FORMAT) {
- style = SHORT_STYLE;
- destination = dataBundle.shortData;
- } else if (patternsTableKey == PatternsTableKey.PATTERNS_SHORT
- && formatsTableKey == FormatsTableKey.CURRENCY_FORMAT) {
- style = SHORT_CURRENCY_STYLE;
- destination = dataBundle.shortCurrencyData;
- } else {
- // Silently ignore this case
- continue;
- }
-
- // SPECIAL CASE: RULES FOR WHETHER OR NOT TO CONSUME THIS TABLE:
- // 1) Don't consume longData if shortData was consumed from the non-Latin
- // locale numbering system
- // 2) Don't consume longData for the first time if this is the root bundle and
- // shortData is already populated from a more specific locale. Note that if
- // both longData and shortData are both only in root, longData will be
- // consumed since it is alphabetically before shortData in the bundle.
- if (isFallback
- && style == LONG_STYLE
- && !dataBundle.shortData.isEmpty()
- && !dataBundle.shortData.fromFallback) {
- continue;
- }
- if (isRoot
- && style == LONG_STYLE
- && dataBundle.longData.isEmpty()
- && !dataBundle.shortData.isEmpty()) {
- continue;
- }
-
- // Set the "fromFallback" flag on the data object
- destination.fromFallback = isFallback;
-
- // traverse into the table of powers of ten
- UResource.Table powersOfTenTable = value.getTable();
- for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
-
- // This value will always be some even power of 10. e.g 10000.
- long power10 = Long.parseLong(key.toString());
- int log10Value = (int) Math.log10(power10);
-
- // Silently ignore divisors that are too big.
- if (log10Value >= 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) {
- // TODO: Use StandardPlural rather than String.
- String pluralVariant = key.toString();
- String template = value.toString();
-
- // Copy the data into the in-memory data bundle (do not overwrite
- // existing values)
- int numZeros = populatePrefixSuffix(
- pluralVariant, log10Value, template, locale, style, destination, false);
-
- // If populatePrefixSuffix returns -1, it means that this key has been
- // encountered already.
- if (numZeros < 0) {
- continue;
- }
-
- // Set the divisor, which is based on the number of zeros in the template
- // string. If the divisor from here is different from the one previously
- // stored, it means that the number of zeros in different plural variants
- // differs; throw an exception.
- long divisor = calculateDivisor(power10, numZeros);
- if (destination.divisors[log10Value] != 0L
- && destination.divisors[log10Value] != divisor) {
- throw new IllegalArgumentException("Plural variant '" + pluralVariant
- + "' template '" + template
- + "' for 10^" + log10Value
- + " has wrong number of zeros in " + localeAndStyle(locale, style));
- }
- destination.divisors[log10Value] = divisor;
- }
- }
- }
- }
- }
- }
-
- /**
- * 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);
- }
- return result;
- }
-
- private static DataBundle load(ULocale ulocale) throws MissingResourceException {
- DataBundle dataBundle = DataBundle.createEmpty();
- String nsName = NumberingSystem.getInstance(ulocale).getName();
- ICUResourceBundle r = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
- ulocale);
- CompactDecimalDataSink sink = new CompactDecimalDataSink(dataBundle, ulocale);
- sink.isFallback = false;
-
- // First load the number elements data from nsName if nsName is not Latin.
- if (!nsName.equals(LATIN_NUMBERING_SYSTEM)) {
- sink.isLatin = false;
-
- try {
- r.getAllItemsWithFallback(NUMBER_ELEMENTS + "/" + nsName, sink);
- } catch (MissingResourceException e) {
- // Silently ignore and use Latin
- }
-
- // Set the "isFallback" flag for when we read Latin
- sink.isFallback = true;
- }
-
- // Now load Latin, which will fill in things that were left out from above.
- sink.isLatin = true;
- r.getAllItemsWithFallback(NUMBER_ELEMENTS + "/" + LATIN_NUMBERING_SYSTEM, sink);
-
- // If longData is empty, default it to be equal to shortData
- if (dataBundle.longData.isEmpty()) {
- dataBundle.longData = dataBundle.shortData;
- }
-
- // Check for "other" variants in each of the three data classes
- checkForOtherVariants(dataBundle.longData, ulocale, LONG_STYLE);
- checkForOtherVariants(dataBundle.shortData, ulocale, SHORT_STYLE);
- checkForOtherVariants(dataBundle.shortCurrencyData, ulocale, SHORT_CURRENCY_STYLE);
-
- // Resolve missing elements
- fillInMissing(dataBundle.longData);
- fillInMissing(dataBundle.shortData);
- fillInMissing(dataBundle.shortCurrencyData);
-
- // Return the data bundle
- return dataBundle;
- }
-
-
- /**
- * 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 destination Extracted prefix and suffix stored here.
- * @return number of zeros found before any decimal point in template, or -1 if it was not saved.
- */
- private static int populatePrefixSuffix(
- String pluralVariant, int idx, String template, ULocale locale, String style,
- Data destination, boolean overwrite) {
- int firstIdx = template.indexOf("0");
- int lastIdx = template.lastIndexOf("0");
- if (firstIdx == -1) {
- throw new IllegalArgumentException(
- "Expect at least one zero in template '" + template +
- "' for variant '" +pluralVariant + "' for 10^" + idx +
- " in " + localeAndStyle(locale, style));
- }
- String prefix = template.substring(0, firstIdx);
- String suffix = template.substring(lastIdx + 1);
-
- // Save the unit, and return -1 if it was not saved
- boolean saved = saveUnit(new DecimalFormat.Unit(prefix, suffix), pluralVariant, idx, destination.units, overwrite);
- if (!saved) {
- return -1;
- }
-
- // If there is effectively no prefix or suffix, ignore the actual
- // number of 0's and act as if the number of 0's matches the size
- // of the number
- if (prefix.trim().length() == 0 && suffix.trim().length() == 0) {
- return idx + 1;
- }
-
- // Calculate number of zeros before decimal point.
- int i = firstIdx + 1;
- while (i <= lastIdx && template.charAt(i) == '0') {
- i++;
- }
- return i - firstIdx;
- }
-
- /**
- * Calculate a divisor based on the magnitude and number of zeros in the
- * template string.
- * @param power10
- * @param numZeros
- * @return
- */
- private static long calculateDivisor(long power10, int numZeros) {
- // 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 = power10;
- for (int i = 1; i < numZeros; i++) {
- divisor /= 10;
- }
- return divisor;
- }
-
-
- /**
- * Returns locale and style. Used to form useful messages in thrown exceptions.
- *
- * Note: This is not covered by unit tests since no exceptions are thrown on the default CLDR data. It is too
- * cumbersome to cover via reflection.
- *
- * @param locale the locale
- * @param style the style
- */
- private static String localeAndStyle(ULocale locale, String style) {
- return "locale '" + locale + "' style '" + style + "'";
- }
-
- /**
- * Checks to make sure that an "other" variant is present in all powers of 10.
- * @param data
- */
- private static void checkForOtherVariants(Data data, ULocale locale, String style) {
- DecimalFormat.Unit[] otherByBase = data.units.get(OTHER);
-
- if (otherByBase == null) {
- throw new IllegalArgumentException("No 'other' plural variants defined in "
- + localeAndStyle(locale, style));
- }
-
- // Check all other plural variants, and make sure that if any of them are populated, then
- // other is also populated
- for (Map.Entry<String, Unit[]> entry : data.units.entrySet()) {
- if (entry.getKey() == OTHER) continue;
- DecimalFormat.Unit[] variantByBase = entry.getValue();
- for (int log10Value = 0; log10Value < MAX_DIGITS; log10Value++) {
- if (variantByBase[log10Value] != null && otherByBase[log10Value] == null) {
- throw new IllegalArgumentException(
- "No 'other' plural variant defined for 10^" + log10Value
- + " but a '" + entry.getKey() + "' variant is defined"
- + " in " +localeAndStyle(locale, style));
- }
- }
- }
- }
-
- /**
- * After reading information from resource bundle into a Data object, there
- * 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;
- for (int i = 0; i < result.divisors.length; i++) {
- if (result.units.get(OTHER)[i] == null) {
- result.divisors[i] = lastDivisor;
- copyFromPreviousIndex(i, result.units);
- } else {
- lastDivisor = result.divisors[i];
- propagateOtherToMissing(i, result.units);
- }
- }
- }
-
- private static void propagateOtherToMissing(
- int idx, Map<String, DecimalFormat.Unit[]> units) {
- DecimalFormat.Unit otherVariantValue = units.get(OTHER)[idx];
- for (DecimalFormat.Unit[] byBase : units.values()) {
- if (byBase[idx] == null) {
- byBase[idx] = otherVariantValue;
- }
- }
- }
-
- private static void copyFromPreviousIndex(int idx, Map<String, DecimalFormat.Unit[]> units) {
- for (DecimalFormat.Unit[] byBase : units.values()) {
- if (idx == 0) {
- byBase[idx] = DecimalFormat.NULL_UNIT;
- } else {
- byBase[idx] = byBase[idx - 1];
- }
- }
- }
-
- private static boolean saveUnit(
- DecimalFormat.Unit unit, String pluralVariant, int idx,
- Map<String, DecimalFormat.Unit[]> units,
- boolean overwrite) {
- DecimalFormat.Unit[] byBase = units.get(pluralVariant);
- if (byBase == null) {
- byBase = new DecimalFormat.Unit[MAX_DIGITS];
- units.put(pluralVariant, byBase);
- }
-
- // Don't overwrite a pre-existing value unless the "overwrite" flag is true.
- if (!overwrite && byBase[idx] != null) {
- return false;
- }
-
- // Save the value and return
- byBase[idx] = unit;
- return true;
- }
-
- /**
- * 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 DecimalFormat.Unit getUnit(
- Map<String, DecimalFormat.Unit[]> units, String variant, int base) {
- DecimalFormat.Unit[] byBase = units.get(variant);
- if (byBase == null) {
- byBase = units.get(CompactDecimalDataCache.OTHER);
- }
- return byBase[base];
- }
-}

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b