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