2 *******************************************************************************
3 * Copyright (C) 2008-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.impl;
9 import java.text.ParseException;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.Iterator;
14 import java.util.MissingResourceException;
16 import java.util.TreeMap;
18 import com.ibm.icu.text.PluralRules;
19 import com.ibm.icu.text.PluralRules.PluralType;
20 import com.ibm.icu.util.ULocale;
21 import com.ibm.icu.util.UResourceBundle;
24 * Loader for plural rules data.
26 public class PluralRulesLoader extends PluralRules.Factory {
27 private final Map<String, PluralRules> rulesIdToRules;
28 // lazy init, use getLocaleIdToRulesIdMap to access
29 private Map<String, String> localeIdToCardinalRulesId;
30 private Map<String, String> localeIdToOrdinalRulesId;
31 private Map<String, ULocale> rulesIdToEquivalentULocale;
34 * Access through singleton.
36 private PluralRulesLoader() {
37 rulesIdToRules = new HashMap<String, PluralRules>();
41 * Returns the locales for which we have plurals data. Utility for testing.
43 public ULocale[] getAvailableULocales() {
44 Set<String> keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
45 ULocale[] locales = new ULocale[keys.size()];
47 for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
48 locales[n++] = ULocale.createCanonical(iter.next());
54 * Returns the functionally equivalent locale.
56 public ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
57 if (isAvailable != null && isAvailable.length > 0) {
58 String localeId = ULocale.canonicalize(locale.getBaseName());
59 Map<String, String> idMap = getLocaleIdToRulesIdMap(PluralType.CARDINAL);
60 isAvailable[0] = idMap.containsKey(localeId);
63 String rulesId = getRulesIdForLocale(locale, PluralType.CARDINAL);
64 if (rulesId == null || rulesId.trim().length() == 0) {
65 return ULocale.ROOT; // ultimate fallback
68 ULocale result = getRulesIdToEquivalentULocaleMap().get(
71 return ULocale.ROOT; // ultimate fallback
78 * Returns the lazily-constructed map.
80 private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) {
81 checkBuildRulesIdMaps();
82 return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
86 * Returns the lazily-constructed map.
88 private Map<String, ULocale> getRulesIdToEquivalentULocaleMap() {
89 checkBuildRulesIdMaps();
90 return rulesIdToEquivalentULocale;
94 * Lazily constructs the localeIdToRulesId and rulesIdToEquivalentULocale
95 * maps if necessary. These exactly reflect the contents of the locales
96 * resource in plurals.res.
98 private void checkBuildRulesIdMaps() {
100 synchronized (this) {
101 haveMap = localeIdToCardinalRulesId != null;
104 Map<String, String> tempLocaleIdToCardinalRulesId;
105 Map<String, String> tempLocaleIdToOrdinalRulesId;
106 Map<String, ULocale> tempRulesIdToEquivalentULocale;
108 UResourceBundle pluralb = getPluralBundle();
109 // Read cardinal-number rules.
110 UResourceBundle localeb = pluralb.get("locales");
112 // sort for convenience of getAvailableULocales
113 tempLocaleIdToCardinalRulesId = new TreeMap<String, String>();
115 tempRulesIdToEquivalentULocale = new HashMap<String, ULocale>();
117 for (int i = 0; i < localeb.getSize(); ++i) {
118 UResourceBundle b = localeb.get(i);
119 String id = b.getKey();
120 String value = b.getString().intern();
121 tempLocaleIdToCardinalRulesId.put(id, value);
123 if (!tempRulesIdToEquivalentULocale.containsKey(value)) {
124 tempRulesIdToEquivalentULocale.put(value, new ULocale(id));
128 // Read ordinal-number rules.
129 localeb = pluralb.get("locales_ordinals");
130 tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>();
131 for (int i = 0; i < localeb.getSize(); ++i) {
132 UResourceBundle b = localeb.get(i);
133 String id = b.getKey();
134 String value = b.getString().intern();
135 tempLocaleIdToOrdinalRulesId.put(id, value);
137 } catch (MissingResourceException e) {
138 // dummy so we don't try again
139 tempLocaleIdToCardinalRulesId = Collections.emptyMap();
140 tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
141 tempRulesIdToEquivalentULocale = Collections.emptyMap();
145 if (localeIdToCardinalRulesId == null) {
146 localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
147 localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId;
148 rulesIdToEquivalentULocale = tempRulesIdToEquivalentULocale;
155 * Gets the rulesId from the locale,with locale fallback. If there is no
156 * rulesId, return null. The rulesId might be the empty string if the rule
157 * is the default rule.
159 public String getRulesIdForLocale(ULocale locale, PluralType type) {
160 Map<String, String> idMap = getLocaleIdToRulesIdMap(type);
161 String localeId = ULocale.canonicalize(locale.getBaseName());
162 String rulesId = null;
163 while (null == (rulesId = idMap.get(localeId))) {
164 int ix = localeId.lastIndexOf("_");
168 localeId = localeId.substring(0, ix);
174 * Gets the rule from the rulesId. If there is no rule for this rulesId,
177 public PluralRules getRulesForRulesId(String rulesId) {
178 // synchronize on the map. release the lock temporarily while we build the rules.
179 PluralRules rules = null;
180 boolean hasRules; // Separate boolean because stored rules can be null.
181 synchronized (rulesIdToRules) {
182 hasRules = rulesIdToRules.containsKey(rulesId);
184 rules = rulesIdToRules.get(rulesId); // can be null
189 UResourceBundle pluralb = getPluralBundle();
190 UResourceBundle rulesb = pluralb.get("rules");
191 UResourceBundle setb = rulesb.get(rulesId);
193 StringBuilder sb = new StringBuilder();
194 for (int i = 0; i < setb.getSize(); ++i) {
195 UResourceBundle b = setb.get(i);
199 sb.append(b.getKey());
201 sb.append(b.getString());
203 rules = PluralRules.parseDescription(sb.toString());
204 } catch (ParseException e) {
205 } catch (MissingResourceException e) {
207 synchronized (rulesIdToRules) {
208 if (rulesIdToRules.containsKey(rulesId)) {
209 rules = rulesIdToRules.get(rulesId);
211 rulesIdToRules.put(rulesId, rules); // can be null
219 * Return the plurals resource. Note MissingResourceException is unchecked,
220 * listed here for clarity. Callers should handle this exception.
222 public UResourceBundle getPluralBundle() throws MissingResourceException {
223 return ICUResourceBundle.getBundleInstance(
224 ICUResourceBundle.ICU_BASE_NAME, "plurals",
225 ICUResourceBundle.ICU_DATA_CLASS_LOADER, true);
229 * Returns the plural rules for the the locale. If we don't have data,
230 * com.ibm.icu.text.PluralRules.DEFAULT is returned.
232 public PluralRules forLocale(ULocale locale, PluralRules.PluralType type) {
233 String rulesId = getRulesIdForLocale(locale, type);
234 if (rulesId == null || rulesId.trim().length() == 0) {
235 return PluralRules.DEFAULT;
237 PluralRules rules = getRulesForRulesId(rulesId);
239 rules = PluralRules.DEFAULT;
245 * The only instance of the loader.
247 public static final PluralRulesLoader loader = new PluralRulesLoader();
250 * @see com.ibm.icu.text.PluralRules.Factory#hasOverride(com.ibm.icu.util.ULocale)
253 public boolean hasOverride(ULocale locale) {