com.ibm.icu.impl.PluralRulesLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
/*
*******************************************************************************
* 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 rulesIdToRules;
// lazy init, use getLocaleIdToRulesIdMap to access
private Map localeIdToCardinalRulesId;
private Map localeIdToOrdinalRulesId;
private Map rulesIdToEquivalentULocale;
private static Map localeIdToPluralRanges;
/**
* Access through singleton.
*/
private PluralRulesLoader() {
rulesIdToRules = new HashMap();
}
/**
* Returns the locales for which we have plurals data. Utility for testing.
*/
public ULocale[] getAvailableULocales() {
Set keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
ULocale[] locales = new ULocale[keys.size()];
int n = 0;
for (Iterator 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 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 getLocaleIdToRulesIdMap(PluralType type) {
checkBuildRulesIdMaps();
return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
}
/**
* Returns the lazily-constructed map.
*/
private Map 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 tempLocaleIdToCardinalRulesId;
Map tempLocaleIdToOrdinalRulesId;
Map tempRulesIdToEquivalentULocale;
try {
UResourceBundle pluralb = getPluralBundle();
// Read cardinal-number rules.
UResourceBundle localeb = pluralb.get("locales");
// sort for convenience of getAvailableULocales
tempLocaleIdToCardinalRulesId = new TreeMap();
// not visible
tempRulesIdToEquivalentULocale = new HashMap();
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();
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 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 tempLocaleIdToPluralRanges = new HashMap();
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);
}
}