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