blob: 8ed630d0589dbada48bc9788c52d0362012779d9 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2008-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeMap;
import com.ibm.icu.text.PluralRanges;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.text.PluralRules.StandardPluralCategories;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
* Loader for plural rules data.
*/
public class PluralRulesLoader extends PluralRules.Factory {
private final Map<String, PluralRules> rulesIdToRules;
// lazy init, use getLocaleIdToRulesIdMap to access
private Map<String, String> localeIdToCardinalRulesId;
private Map<String, String> localeIdToOrdinalRulesId;
private Map<String, ULocale> rulesIdToEquivalentULocale;
private static Map<String, PluralRanges> localeIdToPluralRanges;
/**
* Access through singleton.
*/
private PluralRulesLoader() {
rulesIdToRules = new HashMap<String, PluralRules>();
}
/**
* Returns the locales for which we have plurals data. Utility for testing.
*/
public ULocale[] getAvailableULocales() {
Set<String> keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
ULocale[] locales = new ULocale[keys.size()];
int n = 0;
for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
locales[n++] = ULocale.createCanonical(iter.next());
}
return locales;
}
/**
* Returns the functionally equivalent locale.
*/
public ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
if (isAvailable != null && isAvailable.length > 0) {
String localeId = ULocale.canonicalize(locale.getBaseName());
Map<String, String> idMap = getLocaleIdToRulesIdMap(PluralType.CARDINAL);
isAvailable[0] = idMap.containsKey(localeId);
}
String rulesId = getRulesIdForLocale(locale, PluralType.CARDINAL);
if (rulesId == null || rulesId.trim().length() == 0) {
return ULocale.ROOT; // ultimate fallback
}
ULocale result = getRulesIdToEquivalentULocaleMap().get(
rulesId);
if (result == null) {
return ULocale.ROOT; // ultimate fallback
}
return result;
}
/**
* Returns the lazily-constructed map.
*/
private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) {
checkBuildRulesIdMaps();
return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
}
/**
* Returns the lazily-constructed map.
*/
private Map<String, ULocale> getRulesIdToEquivalentULocaleMap() {
checkBuildRulesIdMaps();
return rulesIdToEquivalentULocale;
}
/**
* Lazily constructs the localeIdToRulesId and rulesIdToEquivalentULocale
* maps if necessary. These exactly reflect the contents of the locales
* resource in plurals.res.
*/
private void checkBuildRulesIdMaps() {
boolean haveMap;
synchronized (this) {
haveMap = localeIdToCardinalRulesId != null;
}
if (!haveMap) {
Map<String, String> tempLocaleIdToCardinalRulesId;
Map<String, String> tempLocaleIdToOrdinalRulesId;
Map<String, ULocale> tempRulesIdToEquivalentULocale;
try {
UResourceBundle pluralb = getPluralBundle();
// Read cardinal-number rules.
UResourceBundle localeb = pluralb.get("locales");
// sort for convenience of getAvailableULocales
tempLocaleIdToCardinalRulesId = new TreeMap<String, String>();
// not visible
tempRulesIdToEquivalentULocale = new HashMap<String, ULocale>();
for (int i = 0; i < localeb.getSize(); ++i) {
UResourceBundle b = localeb.get(i);
String id = b.getKey();
String value = b.getString().intern();
tempLocaleIdToCardinalRulesId.put(id, value);
if (!tempRulesIdToEquivalentULocale.containsKey(value)) {
tempRulesIdToEquivalentULocale.put(value, new ULocale(id));
}
}
// Read ordinal-number rules.
localeb = pluralb.get("locales_ordinals");
tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>();
for (int i = 0; i < localeb.getSize(); ++i) {
UResourceBundle b = localeb.get(i);
String id = b.getKey();
String value = b.getString().intern();
tempLocaleIdToOrdinalRulesId.put(id, value);
}
} catch (MissingResourceException e) {
// dummy so we don't try again
tempLocaleIdToCardinalRulesId = Collections.emptyMap();
tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
tempRulesIdToEquivalentULocale = Collections.emptyMap();
}
synchronized(this) {
if (localeIdToCardinalRulesId == null) {
localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId;
rulesIdToEquivalentULocale = tempRulesIdToEquivalentULocale;
}
}
}
}
/**
* Gets the rulesId from the locale,with locale fallback. If there is no
* rulesId, return null. The rulesId might be the empty string if the rule
* is the default rule.
*/
public String getRulesIdForLocale(ULocale locale, PluralType type) {
Map<String, String> idMap = getLocaleIdToRulesIdMap(type);
String localeId = ULocale.canonicalize(locale.getBaseName());
String rulesId = null;
while (null == (rulesId = idMap.get(localeId))) {
int ix = localeId.lastIndexOf("_");
if (ix == -1) {
break;
}
localeId = localeId.substring(0, ix);
}
return rulesId;
}
/**
* Gets the rule from the rulesId. If there is no rule for this rulesId,
* return null.
*/
public PluralRules getRulesForRulesId(String rulesId) {
// synchronize on the map. release the lock temporarily while we build the rules.
PluralRules rules = null;
boolean hasRules; // Separate boolean because stored rules can be null.
synchronized (rulesIdToRules) {
hasRules = rulesIdToRules.containsKey(rulesId);
if (hasRules) {
rules = rulesIdToRules.get(rulesId); // can be null
}
}
if (!hasRules) {
try {
UResourceBundle pluralb = getPluralBundle();
UResourceBundle rulesb = pluralb.get("rules");
UResourceBundle setb = rulesb.get(rulesId);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < setb.getSize(); ++i) {
UResourceBundle b = setb.get(i);
if (i > 0) {
sb.append("; ");
}
sb.append(b.getKey());
sb.append(": ");
sb.append(b.getString());
}
rules = PluralRules.parseDescription(sb.toString());
} catch (ParseException e) {
} catch (MissingResourceException e) {
}
synchronized (rulesIdToRules) {
if (rulesIdToRules.containsKey(rulesId)) {
rules = rulesIdToRules.get(rulesId);
} else {
rulesIdToRules.put(rulesId, rules); // can be null
}
}
}
return rules;
}
/**
* Return the plurals resource. Note MissingResourceException is unchecked,
* listed here for clarity. Callers should handle this exception.
*/
public UResourceBundle getPluralBundle() throws MissingResourceException {
return ICUResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_BASE_NAME, "plurals",
ICUResourceBundle.ICU_DATA_CLASS_LOADER, true);
}
/**
* Returns the plural rules for the the locale. If we don't have data,
* com.ibm.icu.text.PluralRules.DEFAULT is returned.
*/
public PluralRules forLocale(ULocale locale, PluralRules.PluralType type) {
String rulesId = getRulesIdForLocale(locale, type);
if (rulesId == null || rulesId.trim().length() == 0) {
return PluralRules.DEFAULT;
}
PluralRules rules = getRulesForRulesId(rulesId);
if (rules == null) {
rules = PluralRules.DEFAULT;
}
return rules;
}
/**
* The only instance of the loader.
*/
public static final PluralRulesLoader loader = new PluralRulesLoader();
/* (non-Javadoc)
* @see com.ibm.icu.text.PluralRules.Factory#hasOverride(com.ibm.icu.util.ULocale)
*/
@Override
public boolean hasOverride(ULocale locale) {
return false;
}
private static final PluralRanges UNKNOWN_RANGE = new PluralRanges().freeze();
public PluralRanges getPluralRanges(ULocale locale) {
// TODO markdavis Fix the bad fallback, here and elsewhere in this file.
String localeId = ULocale.canonicalize(locale.getBaseName());
PluralRanges result;
while (null == (result = localeIdToPluralRanges.get(localeId))) {
int ix = localeId.lastIndexOf("_");
if (ix == -1) {
result = UNKNOWN_RANGE;
break;
}
localeId = localeId.substring(0, ix);
}
return result;
}
public boolean isPluralRangesAvailable(ULocale locale) {
return getPluralRanges(locale) == UNKNOWN_RANGE;
}
// TODO markdavis FIX HARD-CODED HACK once we have data from CLDR in the bundles
static {
String[][] pluralRangeData = {
{"locales", "id ja km ko lo ms my th vi zh"},
{"other", "other", "other"},
{"locales", "am bn fr gu hi hy kn mr pa zu"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "other", "other"},
{"locales", "fa"},
{"one", "one", "other"},
{"one", "other", "other"},
{"other", "other", "other"},
{"locales", "ka"},
{"one", "other", "one"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "az de el gl hu it kk ky ml mn ne nl pt sq sw ta te tr ug uz"},
{"one", "other", "other"},
{"other", "one", "one"},
{"other", "other", "other"},
{"locales", "af bg ca en es et eu fi nb sv ur"},
{"one", "other", "other"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "da fil is"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "one", "one"},
{"other", "other", "other"},
{"locales", "si"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "mk"},
{"one", "one", "other"},
{"one", "other", "other"},
{"other", "one", "other"},
{"other", "other", "other"},
{"locales", "lv"},
{"zero", "zero", "other"},
{"zero", "one", "one"},
{"zero", "other", "other"},
{"one", "zero", "other"},
{"one", "one", "one"},
{"one", "other", "other"},
{"other", "zero", "other"},
{"other", "one", "one"},
{"other", "other", "other"},
{"locales", "ro"},
{"one", "few", "few"},
{"one", "other", "other"},
{"few", "one", "few"},
{"few", "few", "few"},
{"few", "other", "other"},
{"other", "few", "few"},
{"other", "other", "other"},
{"locales", "hr sr bs"},
{"one", "one", "one"},
{"one", "few", "few"},
{"one", "other", "other"},
{"few", "one", "one"},
{"few", "few", "few"},
{"few", "other", "other"},
{"other", "one", "one"},
{"other", "few", "few"},
{"other", "other", "other"},
{"locales", "sl"},
{"one", "one", "few"},
{"one", "two", "two"},
{"one", "few", "few"},
{"one", "other", "other"},
{"two", "one", "few"},
{"two", "two", "two"},
{"two", "few", "few"},
{"two", "other", "other"},
{"few", "one", "few"},
{"few", "two", "two"},
{"few", "few", "few"},
{"few", "other", "other"},
{"other", "one", "few"},
{"other", "two", "two"},
{"other", "few", "few"},
{"other", "other", "other"},
{"locales", "he"},
{"one", "two", "other"},
{"one", "many", "many"},
{"one", "other", "other"},
{"two", "many", "other"},
{"two", "other", "other"},
{"many", "many", "many"},
{"many", "other", "many"},
{"other", "one", "other"},
{"other", "two", "other"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "cs pl sk"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"few", "few", "few"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "one", "one"},
{"many", "few", "few"},
{"many", "many", "many"},
{"many", "other", "other"},
{"other", "one", "one"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "lt ru uk"},
{"one", "one", "one"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"few", "one", "one"},
{"few", "few", "few"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "one", "one"},
{"many", "few", "few"},
{"many", "many", "many"},
{"many", "other", "other"},
{"other", "one", "one"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "cy"},
{"zero", "one", "one"},
{"zero", "two", "two"},
{"zero", "few", "few"},
{"zero", "many", "many"},
{"zero", "other", "other"},
{"one", "two", "two"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"two", "few", "few"},
{"two", "many", "many"},
{"two", "other", "other"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "other", "other"},
{"other", "one", "one"},
{"other", "two", "two"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
{"locales", "ar"},
{"zero", "one", "zero"},
{"zero", "two", "zero"},
{"zero", "few", "few"},
{"zero", "many", "many"},
{"zero", "other", "other"},
{"one", "two", "other"},
{"one", "few", "few"},
{"one", "many", "many"},
{"one", "other", "other"},
{"two", "few", "few"},
{"two", "many", "many"},
{"two", "other", "other"},
{"few", "few", "few"},
{"few", "many", "many"},
{"few", "other", "other"},
{"many", "few", "few"},
{"many", "many", "many"},
{"many", "other", "other"},
{"other", "one", "other"},
{"other", "two", "other"},
{"other", "few", "few"},
{"other", "many", "many"},
{"other", "other", "other"},
};
PluralRanges pr = null;
String[] locales = null;
HashMap<String, PluralRanges> tempLocaleIdToPluralRanges = new HashMap<String, PluralRanges>();
for (String[] row : pluralRangeData) {
if (row[0].equals("locales")) {
if (pr != null) {
pr.freeze();
for (String locale : locales) {
tempLocaleIdToPluralRanges.put(locale, pr);
}
}
locales = row[1].split(" ");
pr = new PluralRanges();
} else {
pr.add(
StandardPluralCategories.valueOf(row[0]),
StandardPluralCategories.valueOf(row[1]),
StandardPluralCategories.valueOf(row[2]));
}
}
// do last one
for (String locale : locales) {
tempLocaleIdToPluralRanges.put(locale, pr);
}
// now make whole thing immutable
localeIdToPluralRanges = Collections.unmodifiableMap(tempLocaleIdToPluralRanges);
}
}