• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4  *******************************************************************************
5  * Copyright (C) 2008-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  *******************************************************************************
8  */
9 package com.ibm.icu.impl;
10 
11 import java.text.ParseException;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.LinkedHashSet;
16 import java.util.Map;
17 import java.util.MissingResourceException;
18 import java.util.Set;
19 import java.util.TreeMap;
20 
21 import com.ibm.icu.impl.number.range.StandardPluralRanges;
22 import com.ibm.icu.text.PluralRules;
23 import com.ibm.icu.text.PluralRules.PluralType;
24 import com.ibm.icu.util.ULocale;
25 import com.ibm.icu.util.UResourceBundle;
26 
27 /**
28  * Loader for plural rules data.
29  */
30 public class PluralRulesLoader extends PluralRules.Factory {
31     // Key is rules set + ranges set
32     private final Map<String, PluralRules> pluralRulesCache;
33     // lazy init, use getLocaleIdToRulesIdMap to access
34     private Map<String, String> localeIdToCardinalRulesId;
35     private Map<String, String> localeIdToOrdinalRulesId;
36     private Map<String, ULocale> rulesIdToEquivalentULocale;
37 
38 
39     /**
40      * Access through singleton.
41      */
PluralRulesLoader()42     private PluralRulesLoader() {
43         pluralRulesCache = new HashMap<String, PluralRules>();
44     }
45 
46     /**
47      * Returns the locales for which we have plurals data. Utility for testing.
48      */
getAvailableULocales()49     public ULocale[] getAvailableULocales() {
50         Set<String> keys = getLocaleIdToRulesIdMap(PluralType.CARDINAL).keySet();
51         Set<ULocale> locales = new LinkedHashSet<ULocale>(keys.size());
52         for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
53             locales.add(ULocale.createCanonical(iter.next()));
54         }
55         return locales.toArray(new ULocale[0]);
56     }
57 
58     /**
59      * Returns the functionally equivalent locale.
60      */
getFunctionalEquivalent(ULocale locale, boolean[] isAvailable)61     public ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
62         if (isAvailable != null && isAvailable.length > 0) {
63             String localeId = ULocale.canonicalize(locale.getBaseName());
64             Map<String, String> idMap = getLocaleIdToRulesIdMap(PluralType.CARDINAL);
65             isAvailable[0] = idMap.containsKey(localeId);
66         }
67 
68         String rulesId = getRulesIdForLocale(locale, PluralType.CARDINAL);
69         if (rulesId == null || rulesId.trim().length() == 0) {
70             return ULocale.ROOT; // ultimate fallback
71         }
72 
73         ULocale result = getRulesIdToEquivalentULocaleMap().get(
74                 rulesId);
75         if (result == null) {
76             return ULocale.ROOT; // ultimate fallback
77         }
78 
79         return result;
80     }
81 
82     /**
83      * Returns the lazily-constructed map.
84      */
getLocaleIdToRulesIdMap(PluralType type)85     private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) {
86         checkBuildRulesIdMaps();
87         return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId;
88     }
89 
90     /**
91      * Returns the lazily-constructed map.
92      */
getRulesIdToEquivalentULocaleMap()93     private Map<String, ULocale> getRulesIdToEquivalentULocaleMap() {
94         checkBuildRulesIdMaps();
95         return rulesIdToEquivalentULocale;
96     }
97 
98     /**
99      * Lazily constructs the localeIdToRulesId and rulesIdToEquivalentULocale
100      * maps if necessary. These exactly reflect the contents of the locales
101      * resource in plurals.res.
102      */
checkBuildRulesIdMaps()103     private void checkBuildRulesIdMaps() {
104         boolean haveMap;
105         synchronized (this) {
106             haveMap = localeIdToCardinalRulesId != null;
107         }
108         if (!haveMap) {
109             Map<String, String> tempLocaleIdToCardinalRulesId;
110             Map<String, String> tempLocaleIdToOrdinalRulesId;
111             Map<String, ULocale> tempRulesIdToEquivalentULocale;
112             try {
113                 UResourceBundle pluralb = getPluralBundle();
114                 // Read cardinal-number rules.
115                 UResourceBundle localeb = pluralb.get("locales");
116 
117                 // sort for convenience of getAvailableULocales
118                 tempLocaleIdToCardinalRulesId = new TreeMap<String, String>();
119                 // not visible
120                 tempRulesIdToEquivalentULocale = new HashMap<String, ULocale>();
121 
122                 for (int i = 0; i < localeb.getSize(); ++i) {
123                     UResourceBundle b = localeb.get(i);
124                     String id = b.getKey();
125                     String value = b.getString().intern();
126                     tempLocaleIdToCardinalRulesId.put(id, value);
127 
128                     if (!tempRulesIdToEquivalentULocale.containsKey(value)) {
129                         tempRulesIdToEquivalentULocale.put(value, new ULocale(id));
130                     }
131                 }
132 
133                 // Read ordinal-number rules.
134                 localeb = pluralb.get("locales_ordinals");
135                 tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>();
136                 for (int i = 0; i < localeb.getSize(); ++i) {
137                     UResourceBundle b = localeb.get(i);
138                     String id = b.getKey();
139                     String value = b.getString().intern();
140                     tempLocaleIdToOrdinalRulesId.put(id, value);
141                 }
142             } catch (MissingResourceException e) {
143                 // dummy so we don't try again
144                 tempLocaleIdToCardinalRulesId = Collections.emptyMap();
145                 tempLocaleIdToOrdinalRulesId = Collections.emptyMap();
146                 tempRulesIdToEquivalentULocale = Collections.emptyMap();
147             }
148 
149             synchronized(this) {
150                 if (localeIdToCardinalRulesId == null) {
151                     localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId;
152                     localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId;
153                     rulesIdToEquivalentULocale = tempRulesIdToEquivalentULocale;
154                 }
155             }
156         }
157     }
158 
159     /**
160      * Gets the rulesId from the locale,with locale fallback. If there is no
161      * rulesId, return null. The rulesId might be the empty string if the rule
162      * is the default rule.
163      */
getRulesIdForLocale(ULocale locale, PluralType type)164     public String getRulesIdForLocale(ULocale locale, PluralType type) {
165         Map<String, String> idMap = getLocaleIdToRulesIdMap(type);
166         String localeId = ULocale.canonicalize(locale.getBaseName());
167         String rulesId = null;
168         while (null == (rulesId = idMap.get(localeId))) {
169             int ix = localeId.lastIndexOf("_");
170             if (ix == -1) {
171                 break;
172             }
173             localeId = localeId.substring(0, ix);
174         }
175         return rulesId;
176     }
177 
178     /**
179      * Gets the rule from the rulesId. If there is no rule for this rulesId,
180      * return null.
181      */
getOrCreateRulesForLocale(ULocale locale, PluralRules.PluralType type)182     public PluralRules getOrCreateRulesForLocale(ULocale locale, PluralRules.PluralType type) {
183         String rulesId = getRulesIdForLocale(locale, type);
184         if (rulesId == null || rulesId.trim().length() == 0) {
185             return null;
186         }
187         String rangesId = StandardPluralRanges.getSetForLocale(locale);
188         String cacheKey = rulesId + "/" + rangesId; // could end with "/null" (this is OK)
189         // synchronize on the map.  release the lock temporarily while we build the rules.
190         PluralRules rules = null;
191         boolean hasRules;  // Separate boolean because stored rules can be null.
192         synchronized (pluralRulesCache) {
193             hasRules = pluralRulesCache.containsKey(cacheKey);
194             if (hasRules) {
195                 rules = pluralRulesCache.get(cacheKey);  // can be null
196             }
197         }
198         if (!hasRules) {
199             try {
200                 UResourceBundle pluralb = getPluralBundle();
201                 UResourceBundle rulesb = pluralb.get("rules");
202                 UResourceBundle setb = rulesb.get(rulesId);
203 
204                 StringBuilder sb = new StringBuilder();
205                 for (int i = 0; i < setb.getSize(); ++i) {
206                     UResourceBundle b = setb.get(i);
207                     if (i > 0) {
208                         sb.append("; ");
209                     }
210                     sb.append(b.getKey());
211                     sb.append(": ");
212                     sb.append(b.getString());
213                 }
214                 StandardPluralRanges ranges = StandardPluralRanges.forSet(rangesId);
215                 rules = PluralRules.newInternal(sb.toString(), ranges);
216             } catch (ParseException e) {
217             } catch (MissingResourceException e) {
218             }
219             synchronized (pluralRulesCache) {
220                 if (pluralRulesCache.containsKey(cacheKey)) {
221                     rules = pluralRulesCache.get(cacheKey);
222                 } else {
223                     pluralRulesCache.put(cacheKey, rules);  // can be null
224                 }
225             }
226         }
227         return rules;
228     }
229 
230     /**
231      * Return the plurals resource. Note MissingResourceException is unchecked,
232      * listed here for clarity. Callers should handle this exception.
233      */
getPluralBundle()234     public UResourceBundle getPluralBundle() throws MissingResourceException {
235         return ICUResourceBundle.getBundleInstance(
236                 ICUData.ICU_BASE_NAME, "plurals",
237                 ICUResourceBundle.ICU_DATA_CLASS_LOADER, true);
238     }
239 
240     /**
241      * Returns the plural rules for the the locale. If we don't have data,
242      * com.ibm.icu.text.PluralRules.DEFAULT is returned.
243      */
forLocale(ULocale locale, PluralRules.PluralType type)244     public PluralRules forLocale(ULocale locale, PluralRules.PluralType type) {
245         PluralRules rules = getOrCreateRulesForLocale(locale, type);
246         if (rules == null) {
247             rules = PluralRules.DEFAULT;
248         }
249         return rules;
250     }
251 
252     /**
253      * The only instance of the loader.
254      */
255     public static final PluralRulesLoader loader = new PluralRulesLoader();
256 
257     /* (non-Javadoc)
258      * @see com.ibm.icu.text.PluralRules.Factory#hasOverride(com.ibm.icu.util.ULocale)
259      */
260     @Override
hasOverride(ULocale locale)261     public boolean hasOverride(ULocale locale) {
262         return false;
263     }
264 }
265