]> granicus.if.org Git - icu/commitdiff
ICU-13177 Cleaning up Java compact and long names in preparation for C++.
authorShane Carr <shane@unicode.org>
Thu, 14 Sep 2017 23:02:22 +0000 (23:02 +0000)
committerShane Carr <shane@unicode.org>
Thu, 14 Sep 2017 23:02:22 +0000 (23:02 +0000)
X-SVN-Rev: 40418

icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java
icu4j/main/classes/core/src/newapi/NumberFormatterImpl.java
icu4j/main/classes/core/src/newapi/ScientificNotation.java
icu4j/main/classes/core/src/newapi/impl/CompactData.java
icu4j/main/classes/core/src/newapi/impl/LongNameHandler.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterTest.java

index 9c6558ba752dfb0ff9e18a3b45fa0fbdd5b1fdba..653391ca3a46c6ea15b12385d8dec6e45c74fde0 100644 (file)
@@ -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,
index 9e02cf697a64b36a89107f644fe02cd1909b05e5..24a9b8e4f26e544b93ac773a5cf816f2ccdaa448 100644 (file)
@@ -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);
         }
index 283d0631aef0cc8450297bd9d5a0093196b78cda..e31ec8af73ff9bc94ebb349b5ecb69798f8a1108 100644 (file)
@@ -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;
         }
     }
 }
\ No newline at end of file
index f2fb71d92d860309f16da89e2a0dea6c1f061717..8b1c1df80111e1c421aa4faa56ba0eabc43b0aaf 100644 (file)
@@ -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);
+    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;
-  }
 }
index 8658104daab8b9e033240cd82dd2862307fcd23e..cbb67f14d5e2eff5eb641bd6a1cc46051bf8b6f0 100644 (file)
@@ -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);
index 49d428e40a08807ca079b6f7a0b2266a20dad469..44b651fb3251856c06ecd35b8969a878c7058c10 100644 (file)
@@ -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",