]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/PluralRules.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / PluralRules.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2007-2009, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 \r
8 package com.ibm.icu.text;\r
9 \r
10 import com.ibm.icu.impl.PluralRulesLoader;\r
11 import com.ibm.icu.impl.Utility;\r
12 import com.ibm.icu.util.ULocale;\r
13 \r
14 import java.io.Serializable;\r
15 \r
16 import java.text.ParseException;\r
17 import java.util.Collections;\r
18 import java.util.HashSet;\r
19 import java.util.Locale;\r
20 import java.util.Set;\r
21 \r
22 /** \r
23  * <p>Defines rules for mapping positive double values onto a small set of\r
24  * keywords. Serializable so can be used in formatters, which are\r
25  * serializable. Rules are constructed from a text description, consisting\r
26  * of a series of keywords and conditions.  The {@link #select} method\r
27  * examines each condition in order and returns the keyword for the\r
28  * first condition that matches the number.  If none match,\r
29  * {@link #KEYWORD_OTHER} is returned.</p>\r
30  * <p>\r
31  * Examples:<pre>\r
32  *   "one: n is 1; few: n in 2..4"</pre></p>\r
33  * <p>\r
34  * This defines two rules, for 'one' and 'few'.  The condition for\r
35  * 'one' is "n is 1" which means that the number must be equal to\r
36  * 1 for this condition to pass.  The condition for 'few' is\r
37  * "n in 2..4" which means that the number must be between 2 and\r
38  * 4 inclusive - and be an integer - for this condition to pass. All other\r
39  * numbers are assigned the keyword "other" by the default rule.</p>\r
40  * <p><pre>\r
41  *   "zero: n is 0; one: n is 1; zero: n mod 100 in 1..19"</pre>\r
42  * This illustrates that the same keyword can be defined multiple times.\r
43  * Each rule is examined in order, and the first keyword whose condition\r
44  * passes is the one returned.  Also notes that a modulus is applied\r
45  * to n in the last rule.  Thus its condition holds for 119, 219, 319...</p>\r
46  * <p><pre>\r
47  *   "one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14"</pre></p>\r
48  * <p>\r
49  * This illustrates conjunction and negation.  The condition for 'few'\r
50  * has two parts, both of which must be met: "n mod 10 in 2..4" and\r
51  * "n mod 100 not in 12..14".  The first part applies a modulus to n\r
52  * before the test as in the previous example.  The second part applies\r
53  * a different modulus and also uses negation, thus it matches all\r
54  * numbers _not_ in 12, 13, 14, 112, 113, 114, 212, 213, 214...</p>\r
55  * <p>\r
56  * Syntax:<pre>\r
57  * rules         = rule (';' rule)*\r
58  * rule          = keyword ':' condition\r
59  * keyword       = <identifier>\r
60  * condition     = and_condition ('or' and_condition)*\r
61  * and_condition = relation ('and' relation)*\r
62  * relation      = is_relation | in_relation | within_relation | 'n' <EOL>\r
63  * is_relation   = expr 'is' ('not')? value\r
64  * in_relation   = expr ('not')? 'in' range\r
65  * within_relation = expr ('not')? 'within' range\r
66  * expr          = 'n' ('mod' value)?\r
67  * value         = digit+\r
68  * digit         = 0|1|2|3|4|5|6|7|8|9\r
69  * range         = value'..'value\r
70  * </pre></p>\r
71  * <p>\r
72  * The difference between 'in' and 'within' is that 'in' only includes\r
73  * integers in the specified range, while 'within' includes all values.</p>\r
74  * @stable ICU 3.8\r
75  */\r
76 public class PluralRules implements Serializable {\r
77     private static final long serialVersionUID = 1;\r
78 \r
79     private final RuleList rules;\r
80     private final Set keywords;\r
81     private int repeatLimit; // for equality test\r
82 \r
83     // Standard keywords.\r
84 \r
85     /** \r
86      * Common name for the 'zero' plural form. \r
87      * @stable ICU 3.8 \r
88      */\r
89     public static final String KEYWORD_ZERO = "zero";\r
90 \r
91     /** \r
92      * Common name for the 'singular' plural form. \r
93      * @stable ICU 3.8\r
94      */\r
95     public static final String KEYWORD_ONE = "one";\r
96 \r
97     /**\r
98      * Common name for the 'dual' plural form.\r
99      * @stable ICU 3.8\r
100      */\r
101     public static final String KEYWORD_TWO = "two";\r
102 \r
103     /**\r
104      * Common name for the 'paucal' or other special plural form.\r
105      * @stable ICU 3.8\r
106      */\r
107     public static final String KEYWORD_FEW = "few";\r
108 \r
109     /**\r
110      * Common name for the arabic (11 to 99) plural form.\r
111      * @stable ICU 3.8\r
112      */\r
113     public static final String KEYWORD_MANY = "many";\r
114 \r
115     /**\r
116      * Common name for the default plural form.  This name is returned\r
117      * for values to which no other form in the rule applies.  It \r
118      * can additionally be assigned rules of its own.\r
119      * @stable ICU 3.8\r
120      */\r
121     public static final String KEYWORD_OTHER = "other";\r
122 \r
123     /*\r
124      * The set of all characters a valid keyword can start with.\r
125      */\r
126     private static final UnicodeSet START_CHARS = \r
127         new UnicodeSet("[[:ID_Start:][_]]");\r
128 \r
129     /*\r
130      * The set of all characters a valid keyword can contain after \r
131      * the first character.\r
132      */\r
133     private static final UnicodeSet CONT_CHARS = \r
134         new UnicodeSet("[:ID_Continue:]");\r
135 \r
136     /*\r
137      * The default constraint that is always satisfied.\r
138      */\r
139     private static final Constraint NO_CONSTRAINT = new Constraint() {\r
140         private static final long serialVersionUID = 9163464945387899416L;\r
141 \r
142         public boolean isFulfilled(double n) {\r
143             return true;\r
144         }\r
145         public String toString() {\r
146             return "n is any";\r
147         }\r
148 \r
149         public int updateRepeatLimit(int limit) {\r
150             return limit;\r
151         }\r
152       };\r
153 \r
154     /*\r
155      * The default rule that always returns "other".\r
156      */\r
157     private static final Rule DEFAULT_RULE = new Rule() {\r
158         private static final long serialVersionUID = -5677499073940822149L;\r
159 \r
160         public String getKeyword() {\r
161             return KEYWORD_OTHER;\r
162         }\r
163 \r
164         public boolean appliesTo(double n) {\r
165             return true;\r
166         }\r
167 \r
168         public String toString() {\r
169             return "(" + KEYWORD_OTHER + ")";\r
170         }\r
171 \r
172         public int updateRepeatLimit(int limit) {\r
173             return limit;\r
174         }\r
175     };\r
176 \r
177 \r
178     /**\r
179      * The default rules that accept any number and return \r
180      * {@link #KEYWORD_OTHER}.\r
181      * @stable ICU 3.8\r
182      */\r
183     public static final PluralRules DEFAULT =\r
184         new PluralRules(new RuleChain(DEFAULT_RULE));\r
185 \r
186     /**\r
187      * Parses a plural rules description and returns a PluralRules.\r
188      * @param description the rule description.\r
189      * @throws ParseException if the description cannot be parsed.\r
190      *    The exception index is typically not set, it will be -1.\r
191      * @stable ICU 3.8\r
192      */\r
193     public static PluralRules parseDescription(String description) \r
194         throws ParseException {\r
195 \r
196         description = description.trim();\r
197         if (description.length() == 0) {\r
198           return DEFAULT;\r
199         }\r
200 \r
201         return new PluralRules(parseRuleChain(description));\r
202     }\r
203 \r
204     /**\r
205      * Creates a PluralRules from a description if it is parsable,\r
206      * otherwise returns null.\r
207      * @param description the rule description.\r
208      * @return the PluralRules\r
209      * @stable ICU 3.8\r
210      */\r
211     public static PluralRules createRules(String description) {\r
212         try {\r
213             return parseDescription(description);\r
214         } catch(ParseException e) {\r
215             return null;\r
216         }\r
217     }\r
218 \r
219     /*\r
220      * A constraint on a number.\r
221      */\r
222     private interface Constraint extends Serializable {\r
223         /*\r
224          * Returns true if the number fulfills the constraint.\r
225          * @param n the number to test, >= 0.\r
226          */\r
227         boolean isFulfilled(double n);\r
228 \r
229         /*\r
230          * Returns the larger of limit or the limit of this constraint.\r
231          * If the constraint is a simple range test, this is the higher\r
232          * end of the range; if it is a modulo test, this is the modulus.\r
233          *\r
234          * @param limit the target limit\r
235          * @return the new limit\r
236          */\r
237         int updateRepeatLimit(int limit);\r
238     }\r
239 \r
240     /*\r
241      * A pluralization rule.  .\r
242      */\r
243     private interface Rule extends Serializable {\r
244         /* Returns the keyword that names this rule. */\r
245         String getKeyword();\r
246         /* Returns true if the rule applies to the number. */\r
247         boolean appliesTo(double n);\r
248         /* Returns the larger of limit and this rule's limit. */\r
249         int updateRepeatLimit(int limit);\r
250     }\r
251 \r
252     /*\r
253      * A list of rules to apply in order.\r
254      */\r
255     private interface RuleList extends Serializable {\r
256         /* Returns the keyword of the first rule that applies to the number. */\r
257         String select(double n);\r
258 \r
259         /* Returns the set of defined keywords. */\r
260         Set getKeywords();\r
261 \r
262         /* Return the value at which this rulelist starts repeating. */\r
263         int getRepeatLimit();\r
264     }\r
265 \r
266     /*\r
267      * syntax:\r
268      * condition :     or_condition\r
269      *                 and_condition\r
270      * or_condition :  and_condition 'or' condition\r
271      * and_condition : relation\r
272      *                 relation 'and' relation\r
273      * relation :      is_relation\r
274      *                 in_relation\r
275      *                 within_relation\r
276      *                 'n' EOL\r
277      * is_relation :   expr 'is' value\r
278      *                 expr 'is' 'not' value\r
279      * in_relation :   expr 'in' range\r
280      *                 expr 'not' 'in' range\r
281      * within_relation : expr 'within' range\r
282      *                   expr 'not' 'within' range\r
283      * expr :          'n'\r
284      *                 'n' 'mod' value\r
285      * value :         digit+\r
286      * digit :         0|1|2|3|4|5|6|7|8|9\r
287      * range :         value'..'value\r
288      */\r
289     private static Constraint parseConstraint(String description) \r
290         throws ParseException {\r
291 \r
292         description = description.trim().toLowerCase(Locale.ENGLISH);\r
293 \r
294         Constraint result = null;\r
295         String[] or_together = Utility.splitString(description, "or");\r
296         for (int i = 0; i < or_together.length; ++i) {\r
297             Constraint andConstraint = null;\r
298             String[] and_together = Utility.splitString(or_together[i], "and");\r
299             for (int j = 0; j < and_together.length; ++j) {\r
300                 Constraint newConstraint = NO_CONSTRAINT;\r
301 \r
302                 String condition = and_together[j].trim();\r
303                 String[] tokens = Utility.splitWhitespace(condition);\r
304 \r
305                 int mod = 0;\r
306                 boolean inRange = true;\r
307                 boolean integersOnly = true;\r
308                 long lowBound = -1;\r
309                 long highBound = -1;\r
310 \r
311                 boolean isRange = false;\r
312 \r
313                 int x = 0;\r
314                 String t = tokens[x++];\r
315                 if (!"n".equals(t)) {\r
316                     throw unexpected(t, condition);\r
317                 }\r
318                 if (x < tokens.length) {\r
319                     t = tokens[x++];\r
320                     if ("mod".equals(t)) {\r
321                         mod = Integer.parseInt(tokens[x++]);\r
322                         t = nextToken(tokens, x++, condition);\r
323                     }\r
324                     if ("is".equals(t)) {\r
325                         t = nextToken(tokens, x++, condition);\r
326                         if ("not".equals(t)) {\r
327                             inRange = false;\r
328                             t = nextToken(tokens, x++, condition);\r
329                         }\r
330                     } else {\r
331                         isRange = true;\r
332                         if ("not".equals(t)) {\r
333                             inRange = false;\r
334                             t = nextToken(tokens, x++, condition);\r
335                         }\r
336                         if ("in".equals(t)) {\r
337                             t = nextToken(tokens, x++, condition);\r
338                         } else if ("within".equals(t)) {\r
339                             integersOnly = false;\r
340                             t = nextToken(tokens, x++, condition);\r
341                         } else {\r
342                             throw unexpected(t, condition);\r
343                         }\r
344                     }\r
345 \r
346                     if (isRange) {\r
347                         String[] pair = Utility.splitString(t, "..");\r
348                         if (pair.length == 2) {\r
349                             lowBound = Long.parseLong(pair[0]);\r
350                             highBound = Long.parseLong(pair[1]);\r
351                         } else {\r
352                             throw unexpected(t, condition);\r
353                         }\r
354                     } else {\r
355                         lowBound = highBound = Long.parseLong(t);\r
356                     }\r
357 \r
358                     if (x != tokens.length) {\r
359                         throw unexpected(tokens[x], condition);\r
360                     }\r
361 \r
362                     newConstraint = \r
363                         new RangeConstraint(mod, inRange, integersOnly, lowBound, highBound);\r
364                 }\r
365 \r
366                 if (andConstraint == null) {\r
367                     andConstraint = newConstraint;\r
368                 } else {\r
369                     andConstraint = new AndConstraint(andConstraint, \r
370                                                       newConstraint);\r
371                 }\r
372             }\r
373 \r
374             if (result == null) {\r
375                 result = andConstraint;\r
376             } else {\r
377                 result = new OrConstraint(result, andConstraint);\r
378             }\r
379         }\r
380 \r
381         return result;\r
382     }\r
383 \r
384     /* Returns a parse exception wrapping the token and context strings. */\r
385     private static ParseException unexpected(String token, String context) {\r
386         return new ParseException("unexpected token '" + token +\r
387                                   "' in '" + context + "'", -1);\r
388     }\r
389 \r
390     /* \r
391      * Returns the token at x if available, else throws a parse exception.\r
392      */\r
393     private static String nextToken(String[] tokens, int x, String context) \r
394         throws ParseException {\r
395         if (x < tokens.length) {\r
396             return tokens[x];\r
397         }\r
398         throw new ParseException("missing token at end of '" + context + "'", -1);\r
399     }\r
400 \r
401     /*\r
402      * Syntax:\r
403      * rule : keyword ':' condition\r
404      * keyword: <identifier>\r
405      */\r
406     private static Rule parseRule(String description) throws ParseException {\r
407         int x = description.indexOf(':');\r
408         if (x == -1) {\r
409             throw new ParseException("missing ':' in rule description '" +\r
410                                      description + "'", 0);\r
411         }\r
412 \r
413         String keyword = description.substring(0, x).trim();\r
414         if (!isValidKeyword(keyword)) {\r
415           throw new ParseException("keyword '" + keyword +\r
416                                    " is not valid", 0);\r
417         }\r
418 \r
419         description = description.substring(x+1).trim();\r
420         if (description.length() == 0) {\r
421           throw new ParseException("missing constraint in '" + \r
422                                    description + "'", x+1);\r
423         }\r
424         Constraint constraint = parseConstraint(description);\r
425         Rule rule = new ConstrainedRule(keyword, constraint);\r
426         return rule;\r
427     }\r
428 \r
429     /*\r
430      * Syntax:\r
431      * rules : rule\r
432      *         rule ';' rules\r
433      */\r
434     private static RuleChain parseRuleChain(String description) \r
435         throws ParseException {\r
436 \r
437         RuleChain rc = null;\r
438         String[] rules = Utility.split(description, ';');\r
439         for (int i = 0; i < rules.length; ++i) {\r
440             Rule r = parseRule(rules[i].trim());\r
441             if (rc == null) {\r
442                 rc = new RuleChain(r);\r
443             } else {\r
444                 rc = rc.addRule(r);\r
445             }\r
446         }\r
447         return rc;\r
448     }\r
449 \r
450     /*\r
451      * An implementation of Constraint representing a modulus, \r
452      * a range of values, and include/exclude. Provides lots of\r
453      * convenience factory methods.\r
454      */\r
455     private static class RangeConstraint implements Constraint, Serializable {\r
456         private static final long serialVersionUID = 1;\r
457       \r
458         private int mod;\r
459         private boolean inRange;\r
460         private boolean integersOnly;\r
461         private long lowerBound;\r
462         private long upperBound;\r
463 \r
464         public boolean isFulfilled(double n) {\r
465             if (integersOnly && (n - (long)n) != 0.0) {\r
466                 return !inRange;\r
467             }\r
468             if (mod != 0) {\r
469                 n = n % mod;    // java % handles double numerator the way we want\r
470             }\r
471             return inRange == (n >= lowerBound && n <= upperBound);\r
472         }\r
473 \r
474         RangeConstraint(int mod, boolean inRange, boolean integersOnly,\r
475                         long lowerBound, long upperBound) {\r
476             this.mod = mod;\r
477             this.inRange = inRange;\r
478             this.integersOnly = integersOnly;\r
479             this.lowerBound = lowerBound;\r
480             this.upperBound = upperBound;\r
481         }\r
482 \r
483         public int updateRepeatLimit(int limit) {\r
484           int mylimit = mod == 0 ? (int)upperBound : mod;\r
485           return Math.max(mylimit, limit);\r
486         }\r
487 \r
488         public String toString() {\r
489             return "[mod: " + mod + " inRange: " + inRange +\r
490                 " integersOnly: " + integersOnly +\r
491                 " low: " + lowerBound + " high: " + upperBound + "]";\r
492         }\r
493     }\r
494 \r
495     /* Convenience base class for and/or constraints. */\r
496     private static abstract class BinaryConstraint implements Constraint, \r
497                                                    Serializable {\r
498         private static final long serialVersionUID = 1;\r
499         protected final Constraint a;\r
500         protected final Constraint b;\r
501         private final String conjunction;\r
502 \r
503         protected BinaryConstraint(Constraint a, Constraint b, String c) {\r
504             this.a = a;\r
505             this.b = b;\r
506             this.conjunction = c;\r
507         }\r
508 \r
509         public int updateRepeatLimit(int limit) {\r
510             return a.updateRepeatLimit(b.updateRepeatLimit(limit));\r
511         }\r
512 \r
513         public String toString() {\r
514             return a.toString() + conjunction + b.toString();\r
515         }\r
516     }\r
517       \r
518     /* A constraint representing the logical and of two constraints. */\r
519     private static class AndConstraint extends BinaryConstraint {\r
520         private static final long serialVersionUID = 7766999779862263523L;\r
521 \r
522         AndConstraint(Constraint a, Constraint b) {\r
523             super(a, b, " && ");\r
524         }\r
525 \r
526         public boolean isFulfilled(double n) {\r
527             return a.isFulfilled(n) && b.isFulfilled(n);\r
528         }\r
529     }\r
530 \r
531     /* A constraint representing the logical or of two constraints. */\r
532     private static class OrConstraint extends BinaryConstraint {\r
533         private static final long serialVersionUID = 1405488568664762222L;\r
534 \r
535         OrConstraint(Constraint a, Constraint b) {\r
536             super(a, b, " || ");\r
537         }\r
538 \r
539         public boolean isFulfilled(double n) {\r
540             return a.isFulfilled(n) || b.isFulfilled(n);\r
541         }\r
542     }\r
543 \r
544     /*\r
545      * Implementation of Rule that uses a constraint.\r
546      * Provides 'and' and 'or' to combine constraints.  Immutable.\r
547      */\r
548     private static class ConstrainedRule implements Rule, Serializable {\r
549         private static final long serialVersionUID = 1;\r
550         private final String keyword;\r
551         private final Constraint constraint;\r
552 \r
553         public ConstrainedRule(String keyword, Constraint constraint) {\r
554             this.keyword = keyword;\r
555             this.constraint = constraint;\r
556         }\r
557 \r
558         public Rule and(Constraint c) {\r
559             return new ConstrainedRule(keyword, new AndConstraint(constraint, c));\r
560         }\r
561 \r
562         public Rule or(Constraint c) {\r
563             return new ConstrainedRule(keyword, new OrConstraint(constraint, c));\r
564         }\r
565 \r
566         public String getKeyword() {\r
567             return keyword;\r
568         }\r
569 \r
570         public boolean appliesTo(double n) {\r
571             return constraint.isFulfilled(n);\r
572         }\r
573 \r
574         public int updateRepeatLimit(int limit) {\r
575             return constraint.updateRepeatLimit(limit);\r
576         }\r
577 \r
578         public String toString() { \r
579             return keyword + ": " + constraint;\r
580         }\r
581     }\r
582 \r
583     /*\r
584      * Implementation of RuleList that is itself a node in a linked list.\r
585      * Immutable, but supports chaining with 'addRule'.\r
586      */\r
587     private static class RuleChain implements RuleList, Serializable {\r
588         private static final long serialVersionUID = 1;\r
589         private final Rule rule;\r
590         private final RuleChain next;\r
591 \r
592         /** Creates a rule chain with the single rule. */\r
593         public RuleChain(Rule rule) {\r
594             this(rule, null);\r
595         }\r
596 \r
597         private RuleChain(Rule rule, RuleChain next) {\r
598             this.rule = rule;\r
599             this.next = next;\r
600         }\r
601 \r
602         public RuleChain addRule(Rule nextRule) {\r
603             return new RuleChain(nextRule, this);\r
604         }\r
605 \r
606         private Rule selectRule(double n) {\r
607             Rule r = null;\r
608             if (next != null) {\r
609                 r = next.selectRule(n);\r
610             }\r
611             if (r == null && rule.appliesTo(n)) {\r
612                 r = rule;\r
613             }\r
614             return r;\r
615         }\r
616 \r
617         public String select(double n) {\r
618             Rule r = selectRule(n);\r
619             if (r == null) {\r
620                 return KEYWORD_OTHER;\r
621             }\r
622             return r.getKeyword();\r
623         }\r
624 \r
625         public Set getKeywords() {\r
626             Set result = new HashSet();\r
627             result.add(KEYWORD_OTHER);\r
628             RuleChain rc = this;\r
629             while (rc != null) {\r
630                 result.add(rc.rule.getKeyword());\r
631                 rc = rc.next;\r
632             }\r
633             return result;\r
634         }\r
635 \r
636         public int getRepeatLimit() {\r
637           int result = 0;\r
638           RuleChain rc = this;\r
639           while (rc != null) {\r
640             result = rc.rule.updateRepeatLimit(result);\r
641             rc = rc.next;\r
642           }\r
643           return result;\r
644         }\r
645 \r
646         public String toString() {\r
647             String s = rule.toString();\r
648             if (next != null) {\r
649                 s = next.toString() + "; " + s;\r
650             }\r
651             return s;\r
652         }\r
653     }\r
654 \r
655     // -------------------------------------------------------------------------\r
656     // Static class methods.\r
657     // -------------------------------------------------------------------------\r
658 \r
659     /**\r
660      * Provides access to the predefined <code>PluralRules</code> for a given\r
661      * locale.\r
662      * \r
663      * @param locale The locale for which a <code>PluralRules</code> object is\r
664      *   returned.\r
665      * @return The predefined <code>PluralRules</code> object for this locale.\r
666      *   If there's no predefined rules for this locale, the rules\r
667      *   for the closest parent in the locale hierarchy that has one will\r
668      *   be returned.  The final fallback always returns the default\r
669      *   rules.\r
670      * @stable ICU 3.8\r
671      */\r
672     public static PluralRules forLocale(ULocale locale) {\r
673       return PluralRulesLoader.loader.forLocale(locale);\r
674     }\r
675 \r
676     /*\r
677      * Checks whether a token is a valid keyword.\r
678      * \r
679      * @param token the token to be checked\r
680      * @return true if the token is a valid keyword.\r
681      */\r
682      private static boolean isValidKeyword(String token) {\r
683          if (token.length() > 0 && START_CHARS.contains(token.charAt(0))) {\r
684              for (int i = 1; i < token.length(); ++i) {\r
685                  if (!CONT_CHARS.contains(token.charAt(i))) {\r
686                      return false;\r
687                  }\r
688              }\r
689              return true;\r
690          }\r
691          return false;\r
692      }\r
693 \r
694     /*\r
695      * Creates a new <code>PluralRules</code> object.  Immutable.\r
696      */\r
697      private PluralRules(RuleList rules) {\r
698          this.rules = rules;\r
699          this.keywords = Collections.unmodifiableSet(rules.getKeywords());\r
700      }\r
701 \r
702     /**\r
703      * Given a number, returns the keyword of the first rule that applies to\r
704      * the number.\r
705      * \r
706      * @param number The number for which the rule has to be determined.\r
707      * @return The keyword of the selected rule.\r
708      * @stable ICU 4.0\r
709      */\r
710      public String select(double number) {\r
711          return rules.select(number);\r
712      }\r
713 \r
714     /**\r
715      * Returns a set of all rule keywords used in this <code>PluralRules</code>\r
716      * object.  The rule "other" is always present by default.\r
717      * \r
718      * @return The set of keywords.\r
719      * @stable ICU 3.8\r
720      */\r
721     public Set getKeywords() {\r
722         return keywords;\r
723     }\r
724 \r
725     /**\r
726      * Returns the set of locales for which PluralRules are known.\r
727      * @return the set of locales for which PluralRules are known, as a list\r
728      * @draft ICU 4.2\r
729      * @provisional This API might change or be removed in a future release.\r
730      */\r
731     public static ULocale[] getAvailableULocales() {\r
732       return PluralRulesLoader.loader.getAvailableULocales();\r
733     }\r
734 \r
735     /**\r
736      * Returns the 'functionally equivalent' locale with respect to\r
737      * plural rules.  Calling PluralRules.forLocale with the functionally equivalent\r
738      * locale, and with the provided locale, returns rules that behave the same.\r
739      * <br/>\r
740      * All locales with the same functionally equivalent locale have\r
741      * plural rules that behave the same.  This is not exaustive;\r
742      * there may be other locales whose plural rules behave the same\r
743      * that do not have the same equivalent locale.\r
744      *\r
745      * @param locale the locale to check\r
746      * @param isAvailable if not null and of length > 0, this will hold 'true' at\r
747      * index 0 if locale is directly defined (without fallback) as having plural rules\r
748      * @return the functionally-equivalent locale\r
749      * @draft ICU 4.2\r
750      * @provisional This API might change or be removed in a future release.\r
751      */\r
752     public static ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {\r
753         return PluralRulesLoader.loader.getFunctionalEquivalent(locale, isAvailable);\r
754     }\r
755 \r
756     /**\r
757      * {@inheritDoc}\r
758      * @stable ICU 3.8\r
759      */\r
760     public String toString() {\r
761       return "keywords: " + keywords + " rules: " + rules.toString() + \r
762           " limit: " + getRepeatLimit();\r
763     }\r
764 \r
765     /**\r
766      * {@inheritDoc}\r
767      * @stable ICU 3.8\r
768      */\r
769     public int hashCode() {\r
770       return keywords.hashCode();\r
771     }\r
772 \r
773     /**\r
774      * {@inheritDoc}\r
775      * @stable ICU 3.8\r
776      */\r
777     public boolean equals(Object rhs) {\r
778         return rhs instanceof PluralRules && equals((PluralRules)rhs);\r
779     }\r
780 \r
781     /**\r
782      * Return tif rhs is equal to this.\r
783      * @param rhs the PluralRules to compare to.\r
784      * @return true if this and rhs are equal.\r
785      * @stable ICU 3.8\r
786      */\r
787     public boolean equals(PluralRules rhs) {\r
788       if (rhs == null) {\r
789         return false;\r
790       }\r
791       if (rhs == this) { \r
792         return true;\r
793       }\r
794       if (!rhs.getKeywords().equals(keywords)) {\r
795         return false;\r
796       }\r
797 \r
798       int limit = Math.max(getRepeatLimit(), rhs.getRepeatLimit());\r
799       for (int i = 0; i < limit; ++i) {\r
800         if (!select(i).equals(rhs.select(i))) {\r
801           return false;\r
802         }\r
803       }\r
804       return true;\r
805     }\r
806 \r
807     private int getRepeatLimit() {\r
808       if (repeatLimit == 0) {\r
809         repeatLimit = rules.getRepeatLimit() + 1;\r
810       }\r
811       return repeatLimit;\r
812     }\r
813  }\r