1/*
2 *******************************************************************************
3 * Copyright (C) 2009-2012, International Business Machines Corporation and    *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7
8package com.ibm.icu.text;
9
10import java.util.ArrayList;
11import java.util.Locale;
12import java.util.MissingResourceException;
13
14import com.ibm.icu.impl.ICUCache;
15import com.ibm.icu.impl.ICUResourceBundle;
16import com.ibm.icu.impl.SimpleCache;
17import com.ibm.icu.lang.UCharacter;
18import com.ibm.icu.util.ULocale;
19import com.ibm.icu.util.ULocale.Category;
20import com.ibm.icu.util.UResourceBundle;
21import com.ibm.icu.util.UResourceBundleIterator;
22
23
24/**
25 * <code>NumberingSystem</code> is the base class for all number
26 * systems. This class provides the interface for setting different numbering
27 * system types, whether it be a simple alternate digit system such as
28 * Thai digits or Devanagari digits, or an algorithmic numbering system such
29 * as Hebrew numbering or Chinese numbering.
30 *
31 * @author       John Emmons
32 * @stable ICU 4.2
33 */
34public class NumberingSystem {
35
36    /**
37     * Default constructor.  Returns a numbering system that uses the Western decimal
38     * digits 0 through 9.
39     * @stable ICU 4.2
40     */
41    public NumberingSystem() {
42        radix = 10;
43        algorithmic = false;
44        desc = "0123456789";
45        name = "latn";
46    }
47
48    /**
49     * Factory method for creating a numbering system.
50     * @param radix_in The radix for this numbering system.  ICU currently
51     * supports only numbering systems whose radix is 10.
52     * @param isAlgorithmic_in Specifies whether the numbering system is algorithmic
53     * (true) or numeric (false).
54     * @param desc_in String used to describe the characteristics of the numbering
55     * system.  For numeric systems, this string contains the digits used by the
56     * numbering system, in order, starting from zero.  For algorithmic numbering
57     * systems, the string contains the name of the RBNF ruleset in the locale's
58     * NumberingSystemRules section that will be used to format numbers using
59     * this numbering system.
60     * @stable ICU 4.2
61     */
62    public static NumberingSystem getInstance(int radix_in, boolean isAlgorithmic_in, String desc_in ) {
63        return getInstance(null,radix_in,isAlgorithmic_in,desc_in);
64    }
65
66    /**
67     * Factory method for creating a numbering system.
68     * @param name_in The string representing the name of the numbering system.
69     * @param radix_in The radix for this numbering system.  ICU currently
70     * supports only numbering systems whose radix is 10.
71     * @param isAlgorithmic_in Specifies whether the numbering system is algorithmic
72     * (true) or numeric (false).
73     * @param desc_in String used to describe the characteristics of the numbering
74     * system.  For numeric systems, this string contains the digits used by the
75     * numbering system, in order, starting from zero.  For algorithmic numbering
76     * systems, the string contains the name of the RBNF ruleset in the locale's
77     * NumberingSystemRules section that will be used to format numbers using
78     * this numbering system.
79     * @stable ICU 4.6
80     */
81
82    private static NumberingSystem getInstance(String name_in, int radix_in, boolean isAlgorithmic_in, String desc_in ) {
83        if ( radix_in < 2 ) {
84            throw new IllegalArgumentException("Invalid radix for numbering system");
85        }
86
87        if ( !isAlgorithmic_in ) {
88            if ( desc_in.length() != radix_in || !isValidDigitString(desc_in)) {
89                throw new IllegalArgumentException("Invalid digit string for numbering system");
90            }
91        }
92        NumberingSystem ns = new NumberingSystem();
93        ns.radix = radix_in;
94        ns.algorithmic = isAlgorithmic_in;
95        ns.desc = desc_in;
96        ns.name = name_in;
97        return ns;
98    }
99
100    /**
101     * Returns the default numbering system for the specified locale.
102     * @stable ICU 4.2
103     */
104    public static NumberingSystem getInstance(Locale inLocale) {
105        return getInstance(ULocale.forLocale(inLocale));
106    }
107
108    /**
109     * Returns the default numbering system for the specified ULocale.
110     * @stable ICU 4.2
111     */
112    public static NumberingSystem getInstance(ULocale locale) {
113
114        final String[] OTHER_NS_KEYWORDS = { "native", "traditional", "finance" };
115
116        NumberingSystem ns;
117        Boolean nsResolved = true;
118
119        // Check for @numbers
120        String numbersKeyword = locale.getKeywordValue("numbers");
121        if (numbersKeyword != null ) {
122            for ( String keyword : OTHER_NS_KEYWORDS ) {
123                if ( numbersKeyword.equals(keyword)) {
124                    nsResolved = false;
125                    break;
126                }
127            }
128        } else {
129            numbersKeyword = "default";
130            nsResolved = false;
131        }
132
133        if (nsResolved) {
134            ns = getInstanceByName(numbersKeyword);
135            if ( ns != null ) {
136                return ns;
137            } else { // if @numbers keyword points to a bogus numbering system name, we return the default for the locale
138                numbersKeyword = "default";
139                nsResolved = false;
140            }
141        }
142
143        // Attempt to get the numbering system from the cache
144        String baseName = locale.getBaseName();
145        ns = cachedLocaleData.get(baseName+"@numbers="+numbersKeyword);
146        if (ns != null ) {
147            return ns;
148        }
149
150        // Cache miss, create new instance
151
152        String originalNumbersKeyword = numbersKeyword;
153        String resolvedNumberingSystem = null;
154        while (!nsResolved) {
155            try {
156                ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
157                rb = rb.getWithFallback("NumberElements");
158                resolvedNumberingSystem = rb.getStringWithFallback(numbersKeyword);
159                nsResolved = true;
160            } catch (MissingResourceException ex) { // Fall back behavior as defined in TR35
161                 if (numbersKeyword.equals("native") || numbersKeyword.equals("finance")) {
162                     numbersKeyword = "default";
163                 } else if (numbersKeyword.equals("traditional")) {
164                     numbersKeyword = "native";
165                 } else {
166                     nsResolved = true;
167                 }
168            }
169        }
170
171        if (resolvedNumberingSystem != null) {
172            ns = getInstanceByName(resolvedNumberingSystem);
173        }
174
175        if ( ns == null ) {
176            ns = new NumberingSystem();
177        }
178
179        cachedLocaleData.put(baseName+"@numbers="+originalNumbersKeyword, ns);
180        return ns;
181
182    }
183
184    /**
185     * Returns the default numbering system for the default <code>FORMAT</code> locale.
186     * @see Category#FORMAT
187     * @stable ICU 4.2
188     */
189    public static NumberingSystem getInstance() {
190        return getInstance(ULocale.getDefault(Category.FORMAT));
191    }
192
193    /**
194     * Returns a numbering system from one of the predefined numbering systems
195     * known to ICU.  Numbering system names are based on the numbering systems
196     * defined in CLDR.  To get a list of available numbering systems, use the
197     * getAvailableNames method.
198     * @param name The name of the desired numbering system.  Numbering system
199     * names often correspond with the name of the script they are associated
200     * with.  For example, "thai" for Thai digits, "hebr" for Hebrew numerals.
201     * @stable ICU 4.2
202     */
203    public static NumberingSystem getInstanceByName(String name) {
204        int radix;
205        boolean isAlgorithmic;
206        String description;
207
208        // Get the numbering system from the cache
209        NumberingSystem ns = cachedStringData.get(name);
210        if (ns != null ) {
211            return ns;
212        }
213
214        try {
215            UResourceBundle numberingSystemsInfo = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "numberingSystems");
216            UResourceBundle nsCurrent = numberingSystemsInfo.get("numberingSystems");
217            UResourceBundle nsTop = nsCurrent.get(name);
218
219            description = nsTop.getString("desc");
220            UResourceBundle nsRadixBundle = nsTop.get("radix");
221            UResourceBundle nsAlgBundle = nsTop.get("algorithmic");
222            radix = nsRadixBundle.getInt();
223            int algorithmic = nsAlgBundle.getInt();
224
225            isAlgorithmic = ( algorithmic == 1 );
226
227        } catch (MissingResourceException ex) {
228            return null;
229        }
230
231        ns = getInstance(name,radix,isAlgorithmic,description);
232        cachedStringData.put(name, ns);
233        return ns;
234    }
235
236    /**
237     * Returns a string array containing a list of the names of numbering systems
238     * currently known to ICU.
239     * @stable ICU 4.2
240     */
241    public static String [] getAvailableNames() {
242
243            UResourceBundle numberingSystemsInfo = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "numberingSystems");
244            UResourceBundle nsCurrent = numberingSystemsInfo.get("numberingSystems");
245            UResourceBundle temp;
246
247            String nsName;
248            ArrayList<String> output = new ArrayList<String>();
249            UResourceBundleIterator it = nsCurrent.getIterator();
250            while (it.hasNext()) {
251                temp = it.next();
252                nsName = temp.getKey();
253                output.add(nsName);
254            }
255            return output.toArray(new String[output.size()]);
256    }
257
258    /**
259     * Convenience method to determine if a given digit string is valid for use as a
260     * descriptor of a numeric ( non-algorithmic ) numbering system.  In order for
261     * a digit string to be valid, it must meet the following criteria:
262     * 1. Digits must be in Unicode's basic multilingual plane.
263     * @stable ICU 4.2
264     */
265    public static boolean isValidDigitString(String str) {
266
267        int c;
268        int i = 0;
269        UCharacterIterator it = UCharacterIterator.getInstance(str);
270
271        it.setToStart();
272        while ( (c = it.nextCodePoint()) != UCharacterIterator.DONE) {
273            if ( UCharacter.isSupplementary(c)) { // Digits outside the BMP are not currently supported
274                return false;
275            }
276            i++;
277        }
278        if ( i != 10 ) {
279            return false;
280        }
281        return true;
282    }
283
284    /**
285     * Returns the radix of the current numbering system.
286     * @stable ICU 4.2
287     */
288    public int getRadix() {
289        return radix;
290    }
291
292    /**
293     * Returns the description string of the current numbering system.
294     * The description string describes the characteristics of the numbering
295     * system.  For numeric systems, this string contains the digits used by the
296     * numbering system, in order, starting from zero.  For algorithmic numbering
297     * systems, the string contains the name of the RBNF ruleset in the locale's
298     * NumberingSystemRules section that will be used to format numbers using
299     * this numbering system.
300     * @stable ICU 4.2
301     */
302    public String getDescription() {
303        return desc;
304    }
305
306    /**
307     * Returns the string representing the name of the numbering system.
308     * @stable ICU 4.6
309     */
310    public String getName() {
311        return name;
312    }
313    /**
314     * Returns the numbering system's algorithmic status.  If true,
315     * the numbering system is algorithmic and uses an RBNF formatter to
316     * format numerals.  If false, the numbering system is numeric and
317     * uses a fixed set of digits.
318     * @stable ICU 4.2
319     */
320    public boolean isAlgorithmic() {
321        return algorithmic;
322    }
323
324    private String desc;
325    private int radix;
326    private boolean algorithmic;
327    private String name;
328
329    /**
330     * Cache to hold the NumberingSystems by Locale.
331     */
332    private static ICUCache<String, NumberingSystem> cachedLocaleData = new SimpleCache<String, NumberingSystem>();
333
334    /**
335     * Cache to hold the NumberingSystems by name.
336     */
337    private static ICUCache<String, NumberingSystem> cachedStringData = new SimpleCache<String, NumberingSystem>();
338
339}
340