From 59a7dc16de65d5fc9a71aa66233c807784a71d5c Mon Sep 17 00:00:00 2001 From: Travis Keep Date: Fri, 16 Nov 2012 00:44:07 +0000 Subject: [PATCH] ICU-9647 Update J version of CompactDecimalFormat. X-SVN-Rev: 32844 --- .../ibm/icu/text/CompactDecimalDataCache.java | 134 ++++++++++++++---- .../ibm/icu/text/CompactDecimalFormat.java | 31 +++- .../src/com/ibm/icu/text/DecimalFormat.java | 14 +- .../test/format/CompactDecimalFormatTest.java | 41 ++++-- 4 files changed, 179 insertions(+), 41 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java b/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java index 0b22aa4dfed..3a483a7875b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalDataCache.java @@ -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 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:
    + *
  1. local numbering system no ROOT fallback
  2. + *
  3. latin numbering system no ROOT fallback
  4. + *
  5. latin numbering system ROOT locale.
  6. + *
+ * 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(); } /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java index daa3f9dd9dc..94467ab530d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java @@ -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 lhs, Map 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 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 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index 8e7387274b8..baefca8e34f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -347,7 +347,7 @@ import com.ibm.icu.util.ULocale.Category; * DecimalFormat 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). - * + * *

Formatting

* *

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("", ""); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java index 52fb7c71262..71fbd00b52c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java @@ -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; + } } -- 2.40.0