import java.util.HashMap;
import java.util.Map;
+import java.util.MissingResourceException;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
*/
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";
/**
* less than 10^15.
*/
static final int MAX_DIGITS = 15;
-
+
private static final String LATIN_NUMBERING_SYSTEM = "latn";
private final ICUCache<ULocale, DataBundle> cache =
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
/**
* 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.
*/
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.
* 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],
} else {
result.append(ch);
}
-
+
// Update state
switch (state) {
case OUTSIDE:
throw new IllegalStateException();
}
}
- return result.toString();
+ return result.toString();
}
/**
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;
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);
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
{12345678901234f, "T12"},
{12345678901234567890f, "T12000000"},
};
-
+
Object[][] CsTestDataShort = {
{1000, "1\u00a0tis."},
{1500, "1,5\u00a0tis."},
{12712345678901f, "13\u00a0bil."},
{127123456789012f, "130\u00a0bil."},
};
-
+
Object[][] SkTestDataLong = {
{1000, "1 tis\u00edc"},
{1572, "1,6 tis\u00edc"},
{-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);
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);
}
}
public void TestFieldPosition() {
- CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(
+ CompactDecimalFormat cdf = getCDFInstance(
ULocale.forLanguageTag("sw"), CompactStyle.SHORT);
FieldPosition fp = new FieldPosition(0);
StringBuffer sb = new StringBuffer();
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]));
}
}
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;
+ }
}