]> granicus.if.org Git - icu/commitdiff
ICU-9647 Update J version of CompactDecimalFormat.
authorTravis Keep <keep94@gmail.com>
Fri, 16 Nov 2012 00:44:07 +0000 (00:44 +0000)
committerTravis Keep <keep94@gmail.com>
Fri, 16 Nov 2012 00:44:07 +0000 (00:44 +0000)
X-SVN-Rev: 32844

icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java
icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java

index 0b22aa4dfed04dafb1cbd803030dd7074f5f82de..3a483a7875bc5443aaa828c63eb7f1718bf24dc3 100644 (file)
@@ -8,6 +8,7 @@ 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.ICUResourceBundle;
@@ -22,6 +23,12 @@ import com.ibm.icu.util.UResourceBundle;
  */
 class CompactDecimalDataCache {
 
+    private static final String SHORT_STYLE = "short";
+    private static final String LONG_STYLE = "long";
+    private static final String NUMBER_ELEMENTS = "NumberElements";
+    private static final String PATTERN_LONG_PATH = "patternsLong/decimalFormat";
+    private static final String PATTERNS_SHORT_PATH = "patternsShort/decimalFormat";
+
     static final String OTHER = "other";
 
     /**
@@ -29,7 +36,7 @@ class CompactDecimalDataCache {
      * less than 10^15.
      */
     static final int MAX_DIGITS = 15;
-    
+
     private static final String LATIN_NUMBERING_SYSTEM = "latn";
 
     private final ICUCache<ULocale, DataBundle> cache =
@@ -79,13 +86,24 @@ class CompactDecimalDataCache {
             this.longData = longData;
         }
     }
-    
+
     private static enum QuoteState {
         OUTSIDE,   // Outside single quote
         INSIDE_EMPTY,  // Just inside single quote
         INSIDE_FULL   // Inside single quote along with characters
     }
 
+    private static enum DataLocation { // Don't change order
+        LOCAL,  // In local numbering system
+        LATIN,  // In latin numbering system
+        ROOT    // In root locale
+    }
+
+    private static enum UResFlags {
+        ANY,  // Any locale will do.
+        NOT_ROOT  // Locale cannot be root.
+    }
+
 
     /**
      * Fetch data for a particular locale. Clients must not modify any part
@@ -103,10 +121,13 @@ class CompactDecimalDataCache {
 
     /**
      * Loads the "patternsShort" and "patternsLong" data for a particular locale.
-     * We assume that "patternsShort" data can be found for any locale. If we can't
-     * find it we throw an exception. However, we allow "patternsLong" data to be
-     * missing for a locale. In this case, we assume that the "patternsLong" data
-     * is identical to the "paternsShort" data.
+     * We look for both of them in 3 places in this order:<ol>
+     * <li>local numbering system no ROOT fallback</li>
+     * <li>latin numbering system no ROOT fallback</li>
+     * <li>latin numbering system ROOT locale.</li>
+     * </ol>
+     * If we find "patternsShort" data before finding "patternsLong" data, we
+     * make the "patternsLong" data be the same as "patternsShort."
      * @param ulocale the locale for which we are loading the data.
      * @return The returned data, never null.
      */
@@ -114,25 +135,88 @@ class CompactDecimalDataCache {
         NumberingSystem ns = NumberingSystem.getInstance(ulocale);
         ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
                 getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale);
+        r = r.getWithFallback(NUMBER_ELEMENTS);
         String numberingSystemName = ns.getName();
-        Data shortData = null;
-        Data longData = null;
+
+        ICUResourceBundle shortDataBundle = null;
+        ICUResourceBundle longDataBundle = null;
         if (!LATIN_NUMBERING_SYSTEM.equals(numberingSystemName)) {
-            shortData = loadWithStyle(r, numberingSystemName, ulocale, "patternsShort", true);
-            longData = loadWithStyle(r, numberingSystemName, ulocale, "patternsLong", true);
-        }
-        if (shortData == null) {
-          shortData = loadWithStyle(r, LATIN_NUMBERING_SYSTEM, ulocale, "patternsShort", false);
+            ICUResourceBundle bundle = findWithFallback(r, numberingSystemName, UResFlags.NOT_ROOT);
+            shortDataBundle = findWithFallback(bundle, PATTERNS_SHORT_PATH, UResFlags.NOT_ROOT);
+            longDataBundle = findWithFallback(bundle, PATTERN_LONG_PATH, UResFlags.NOT_ROOT);
         }
-        if (longData == null) {
-          longData = loadWithStyle(r, LATIN_NUMBERING_SYSTEM, ulocale, "patternsLong", true);
+
+        // If we haven't found, look in latin numbering system.
+        if (shortDataBundle == null) {
+            ICUResourceBundle bundle = getWithFallback(r, LATIN_NUMBERING_SYSTEM, UResFlags.ANY);
+            shortDataBundle = getWithFallback(bundle, PATTERNS_SHORT_PATH, UResFlags.ANY);
+            if (longDataBundle == null) {
+                longDataBundle = findWithFallback(bundle, PATTERN_LONG_PATH, UResFlags.ANY);
+                if (longDataBundle != null && isRoot(longDataBundle) && !isRoot(shortDataBundle)) {
+                    longDataBundle = null;
+                }
+            }
         }
-        if (longData == null) {
+        Data shortData = loadStyle(shortDataBundle, ulocale, SHORT_STYLE);
+        Data longData;
+        if (longDataBundle == null) {
             longData = shortData;
+        } else {
+            longData = loadStyle(longDataBundle, ulocale, LONG_STYLE);
         }
         return new DataBundle(shortData, longData);
     }
 
+    /**
+     * findWithFallback finds a sub-resource bundle within r.
+     * @param r a resource bundle. It may be null in which case sub-resource bundle
+     *   won't be found.
+     * @param path the path relative to r
+     * @param flags ANY or NOT_ROOT for locale of found sub-resource bundle.
+     * @return The sub-resource bundle or NULL if none found.
+     */
+    private static ICUResourceBundle findWithFallback(
+            ICUResourceBundle r, String path, UResFlags flags) {
+        if (r == null) {
+            return null;
+        }
+        ICUResourceBundle result = r.findWithFallback(path);
+        if (result == null) {
+            return null;
+        }
+        switch (flags) {
+        case NOT_ROOT:
+            return isRoot(result) ? null : result;
+        case ANY:
+            return result;
+        default:
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Like findWithFallback but throws MissingResourceException if no
+     * resource found instead of returning null.
+     */
+    private static ICUResourceBundle getWithFallback(
+            ICUResourceBundle r, String path, UResFlags flags) {
+        ICUResourceBundle result = findWithFallback(r, path, flags);
+        if (result == null) {
+            throw new MissingResourceException(
+                    "Cannot find " + path,
+                    ICUResourceBundle.class.getName(), path);
+
+        }
+        return result;
+    }
+
+    /**
+     * isRoot returns true if r is in root locale or false otherwise.
+     */
+    private static boolean isRoot(ICUResourceBundle r) {
+        return r.getLocale().equals("root");
+    }
+
     /**
      * Loads the data
      * @param r the main resource bundle.
@@ -142,19 +226,7 @@ class CompactDecimalDataCache {
      * if data cannot be found.
      * @return The loaded data or possibly null if allowNullResult is true.
      */
-    private static Data loadWithStyle(
-            ICUResourceBundle r, String numberingSystemName, ULocale locale, String style,
-            boolean allowNullResult) {
-        String resourcePath =
-            "NumberElements/" + numberingSystemName + "/" + style + "/decimalFormat";
-        if (allowNullResult) {
-            r = r.findWithFallback(resourcePath);
-        } else {
-            r = r.getWithFallback(resourcePath);
-        }
-        if (r == null) {
-            return null;
-        }
+    private static Data loadStyle(ICUResourceBundle r, ULocale locale, String style) {
         int size = r.getSize();
         Data result = new Data(
                 new long[MAX_DIGITS],
@@ -303,7 +375,7 @@ class CompactDecimalDataCache {
             } else {
                 result.append(ch);
             }
-            
+
             // Update state
             switch (state) {
             case OUTSIDE:
@@ -317,7 +389,7 @@ class CompactDecimalDataCache {
                 throw new IllegalStateException();
             }
         }
-        return result.toString();  
+        return result.toString();
     }
 
     /**
index daa3f9dd9dce94444c1b49e2e591918c2f319736..94467ab530d651a29f3c567c82af76fa82630289 100644 (file)
@@ -16,6 +16,7 @@ import java.math.BigInteger;
 import java.text.AttributedCharacterIterator;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Locale;
@@ -122,7 +123,7 @@ public class CompactDecimalFormat extends DecimalFormat {
         this.divisor = data.divisors;
         applyPattern(format.toPattern());
         setDecimalFormatSymbols(format.getDecimalFormatSymbols());
-        setMaximumSignificantDigits(2); // default significant digits
+        setMaximumSignificantDigits(3); // default significant digits
         setSignificantDigitsUsed(true);
         setGroupingUsed(false);
         this.pluralRules = PluralRules.forLocale(locale);
@@ -211,6 +212,34 @@ public class CompactDecimalFormat extends DecimalFormat {
         setCurrency(null);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null)
+            return false;
+        if (!super.equals(obj))
+            return false; // super does class check
+        CompactDecimalFormat other = (CompactDecimalFormat) obj;
+        return mapsAreEqual(units, other.units)
+                && Arrays.equals(divisor, other.divisor)
+                && Arrays.equals(currencyAffixes, other.currencyAffixes)
+                && pluralRules.equals(other.pluralRules);
+    }
+
+    private boolean mapsAreEqual(
+        Map<String, DecimalFormat.Unit[]> lhs, Map<String, DecimalFormat.Unit[]> rhs) {
+        if (lhs.size() != rhs.size()) {
+            return false;
+        }
+        // For each MapEntry in lhs, see if there is a matching one in rhs.
+        for (Map.Entry<String, DecimalFormat.Unit[]> entry : lhs.entrySet()) {
+            DecimalFormat.Unit[] value = rhs.get(entry.getKey());
+            if (value == null || !Arrays.equals(entry.getValue(), value)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * {@inheritDoc}
      * @draft ICU 49
index 8e7387274b81bfc1905054eb25af6f74d87dd748..baefca8e34f6d2b6ef97277212e8213109cd3c08 100644 (file)
@@ -347,7 +347,7 @@ import com.ibm.icu.util.ULocale.Category;
  * <code>DecimalFormat</code> internally limits of maximum decimal digits to be 1000. Thus,
  * an input string resulting more than 1000 digits in plain decimal representation (non-exponent)
  * will be treated as either overflow (positive/negative infinite) or underflow (+0.0/-0.0).
- * 
+ *
  * <h4>Formatting</h4>
  *
  * <p>Formatting is guided by several parameters, all of which can be specified either
@@ -5697,6 +5697,18 @@ public class DecimalFormat extends NumberFormat {
         public void writePrefix(StringBuffer toAppendTo) {
             toAppendTo.append(prefix);
         }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof Unit)) {
+                return false;
+            }
+            Unit other = (Unit) obj;
+            return prefix.equals(other.prefix) && suffix.equals(other.suffix);
+        }
     }
 
     static final Unit NULL_UNIT = new Unit("", "");
index 52fb7c71262beb0e912ca7b83b3cb83b2ef6e34d..71fbd00b52c53cf01499f28d215baaec42ecb3a2 100644 (file)
@@ -128,7 +128,7 @@ public class CompactDecimalFormatTest extends TestFmwk {
             {12345678901234f, "T12"},
             {12345678901234567890f, "T12000000"},
     };
-    
+
     Object[][] CsTestDataShort = {
             {1000, "1\u00a0tis."},
             {1500, "1,5\u00a0tis."},
@@ -145,7 +145,7 @@ public class CompactDecimalFormatTest extends TestFmwk {
             {12712345678901f, "13\u00a0bil."},
             {127123456789012f, "130\u00a0bil."},
     };
-  
+
     Object[][] SkTestDataLong = {
             {1000, "1 tis\u00edc"},
             {1572, "1,6 tis\u00edc"},
@@ -167,11 +167,16 @@ public class CompactDecimalFormatTest extends TestFmwk {
             {-12345678901234567890f, "T-12000000"},
     };
 
-    // TODO: Write a test for negative numbers in arabic.
+    public void TestDefaultSignificantDigits() {
+        // We are expecting three significant digits as default.
+        CompactDecimalFormat cdf =
+                CompactDecimalFormat.getInstance(ULocale.ENGLISH, CompactStyle.SHORT);
+        assertEquals("Default significant digits", "12.3K", cdf.format(12345));
+    }
 
     public void TestCharacterIterator() {
         CompactDecimalFormat cdf =
-            CompactDecimalFormat.getInstance(ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
+            getCDFInstance(ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
         AttributedCharacterIterator iter = cdf.formatToCharacterIterator(1234567);
         assertEquals("CharacterIterator", "M1.2", iterToString(iter));
         iter = cdf.formatToCharacterIterator(1234567);
@@ -191,11 +196,11 @@ public class CompactDecimalFormatTest extends TestFmwk {
                         ULocale.forLanguageTag("ar"), CompactStyle.LONG);
         assertEquals("Arabic Long", "\u0665\u066B\u0663- \u0623\u0644\u0641", cdf.format(-5300));
     }
-    
+
     public void TestCsShort() {
         checkLocale(ULocale.forLanguageTag("cs"), CompactStyle.SHORT, CsTestDataShort);
     }
-    
+
     public void TestSkLong() {
         checkLocale(ULocale.forLanguageTag("sk"), CompactStyle.LONG, SkTestDataLong);
     }
@@ -225,7 +230,7 @@ public class CompactDecimalFormatTest extends TestFmwk {
     }
 
     public void TestFieldPosition() {
-        CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(
+        CompactDecimalFormat cdf = getCDFInstance(
                 ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
         FieldPosition fp = new FieldPosition(0);
         StringBuffer sb = new StringBuffer();
@@ -235,8 +240,20 @@ public class CompactDecimalFormatTest extends TestFmwk {
         assertEquals("fp end", 2, fp.getEndIndex());
     }
 
+    public void TestEquals() {
+        CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(
+                ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
+        CompactDecimalFormat equalsCdf = CompactDecimalFormat.getInstance(
+                ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
+        CompactDecimalFormat notEqualsCdf = CompactDecimalFormat.getInstance(
+                ULocale.forLanguageTag("sw"), CompactStyle.LONG);
+        assertEquals("equals", cdf, equalsCdf);
+        assertNotEquals("not equals", cdf, notEqualsCdf);
+
+    }
+
     public void checkLocale(ULocale locale, CompactStyle style, Object[][] testData) {
-        CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(locale, style);
+        CompactDecimalFormat cdf = getCDFInstance(locale, style);
         for (Object[] row : testData) {
             assertEquals(locale + " (" + locale.getDisplayName(locale) + ")", row[1], cdf.format(row[0]));
         }
@@ -249,4 +266,12 @@ public class CompactDecimalFormatTest extends TestFmwk {
         }
         return builder.toString();
     }
+
+    private static CompactDecimalFormat getCDFInstance(ULocale locale, CompactStyle style) {
+        CompactDecimalFormat result = CompactDecimalFormat.getInstance(locale, style);
+        // Our tests are written for two significant digits. We set explicitly here
+        // because default significant digits may change.
+        result.setMaximumSignificantDigits(2);
+        return result;
+    }
 }