1/* 2 ******************************************************************************* 3 * Copyright (C) 2008-2013, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ******************************************************************************* 6 */ 7package com.ibm.icu.simple; 8 9import java.text.ParseException; 10import java.util.Collections; 11import java.util.HashMap; 12import java.util.Locale; 13import java.util.Map; 14import java.util.MissingResourceException; 15import java.util.ResourceBundle; 16import java.util.TreeMap; 17 18import com.ibm.icu.simple.PluralRules.PluralType; 19 20/** 21 * Loader for plural rules data. 22 */ 23public class PluralRulesLoader extends PluralRules.Factory { 24 // Data created from ICU4C with the command 25 // ~/svn.icu/trunk/bld$ LD_LIBRARY_PATH=lib bin/genrb --write-java UTF-8 --java-package com.ibm.icu.simple -s ../src/source/data/misc/ plurals.txt -d /tmp/icu 26 private static final ResourceBundle DATA_RB = new LocaleElements_plurals(); 27 28 private final Map<String, PluralRules> rulesIdToRules; 29 // lazy init, use getLocaleIdToRulesIdMap to access 30 private Map<String, String> localeIdToCardinalRulesId; 31 private Map<String, String> localeIdToOrdinalRulesId; 32 33 /** 34 * Access through singleton. 35 */ 36 private PluralRulesLoader() { 37 rulesIdToRules = new HashMap<String, PluralRules>(); 38 } 39 40 /** 41 * Returns the lazily-constructed map. 42 */ 43 private Map<String, String> getLocaleIdToRulesIdMap(PluralType type) { 44 checkBuildRulesIdMaps(); 45 return (type == PluralType.CARDINAL) ? localeIdToCardinalRulesId : localeIdToOrdinalRulesId; 46 } 47 48 /** 49 * Lazily constructs the localeIdToRulesId and rulesIdToEquivalentULocale 50 * maps if necessary. These exactly reflect the contents of the locales 51 * resource in plurals.res. 52 */ 53 private void checkBuildRulesIdMaps() { 54 boolean haveMap; 55 synchronized (this) { 56 haveMap = localeIdToCardinalRulesId != null; 57 } 58 if (!haveMap) { 59 Map<String, String> tempLocaleIdToCardinalRulesId; 60 Map<String, String> tempLocaleIdToOrdinalRulesId; 61 try { 62 ResourceBundle pluralb = DATA_RB; 63 // Read cardinal-number rules. 64 Object[][] localeb = (Object[][]) pluralb.getObject("locales"); 65 66 // sort for convenience of getAvailableULocales 67 tempLocaleIdToCardinalRulesId = new TreeMap<String, String>(); 68 69 for (Object[] langAndId : localeb) { 70 String id = (String) langAndId[0]; 71 String value = (String) langAndId[1]; 72 tempLocaleIdToCardinalRulesId.put(id, value); 73 } 74 75 // Read ordinal-number rules. 76 localeb = (Object[][]) pluralb.getObject("locales_ordinals"); 77 tempLocaleIdToOrdinalRulesId = new TreeMap<String, String>(); 78 for (Object[] langAndId : localeb) { 79 String id = (String) langAndId[0]; 80 String value = (String) langAndId[1]; 81 tempLocaleIdToOrdinalRulesId.put(id, value); 82 } 83 } catch (MissingResourceException e) { 84 // dummy so we don't try again 85 tempLocaleIdToCardinalRulesId = Collections.emptyMap(); 86 tempLocaleIdToOrdinalRulesId = Collections.emptyMap(); 87 } 88 89 synchronized(this) { 90 if (localeIdToCardinalRulesId == null) { 91 localeIdToCardinalRulesId = tempLocaleIdToCardinalRulesId; 92 localeIdToOrdinalRulesId = tempLocaleIdToOrdinalRulesId; 93 } 94 } 95 } 96 } 97 98 /** 99 * Gets the rulesId from the locale,with locale fallback. If there is no 100 * rulesId, return null. The rulesId might be the empty string if the rule 101 * is the default rule. 102 */ 103 public String getRulesIdForLocale(Locale locale, PluralType type) { 104 Map<String, String> idMap = getLocaleIdToRulesIdMap(type); 105 String lang = locale.getLanguage(); 106 String rulesId = idMap.get(lang); 107 return rulesId; 108 } 109 110 /** 111 * Gets the rule from the rulesId. If there is no rule for this rulesId, 112 * return null. 113 */ 114 public PluralRules getRulesForRulesId(String rulesId) { 115 // synchronize on the map. release the lock temporarily while we build the rules. 116 PluralRules rules = null; 117 boolean hasRules; // Separate boolean because stored rules can be null. 118 synchronized (rulesIdToRules) { 119 hasRules = rulesIdToRules.containsKey(rulesId); 120 if (hasRules) { 121 rules = rulesIdToRules.get(rulesId); // can be null 122 } 123 } 124 if (!hasRules) { 125 try { 126 ResourceBundle pluralb = DATA_RB; 127 Object[][] rulesb = (Object[][]) pluralb.getObject("rules"); 128 Object[][] setb = null; 129 for (Object[] idAndRule : rulesb) { // Unbounded loop: We must find the rulesId. 130 if (rulesId.equals(idAndRule[0])) { 131 setb = (Object[][]) idAndRule[1]; 132 break; 133 } 134 } 135 136 StringBuilder sb = new StringBuilder(); 137 for (Object[] keywordAndRule : setb) { 138 if (sb.length() > 0) { 139 sb.append("; "); 140 } 141 sb.append((String) keywordAndRule[0]); 142 sb.append(": "); 143 sb.append((String) keywordAndRule[1]); 144 } 145 rules = PluralRules.parseDescription(sb.toString()); 146 } catch (ParseException e) { 147 } catch (MissingResourceException e) { 148 } 149 synchronized (rulesIdToRules) { 150 if (rulesIdToRules.containsKey(rulesId)) { 151 rules = rulesIdToRules.get(rulesId); 152 } else { 153 rulesIdToRules.put(rulesId, rules); // can be null 154 } 155 } 156 } 157 return rules; 158 } 159 160 /** 161 * Returns the plural rules for the the locale. If we don't have data, 162 * com.ibm.icu.text.PluralRules.DEFAULT is returned. 163 */ 164 public PluralRules forLocale(Locale locale, PluralRules.PluralType type) { 165 String rulesId = getRulesIdForLocale(locale, type); 166 if (rulesId == null || rulesId.trim().length() == 0) { 167 return PluralRules.DEFAULT; 168 } 169 PluralRules rules = getRulesForRulesId(rulesId); 170 if (rules == null) { 171 rules = PluralRules.DEFAULT; 172 } 173 return rules; 174 } 175 176 /** 177 * The only instance of the loader. 178 */ 179 public static final PluralRulesLoader loader = new PluralRulesLoader(); 180} 181