]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/core/src/com/ibm/icu/text/PluralRules.java
Upgrade ICU4J.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / core / src / com / ibm / icu / text / PluralRules.java
1 /*
2  *******************************************************************************
3  * Copyright (C) 2007-2013, International Business Machines Corporation and
4  * others. All Rights Reserved.
5  *******************************************************************************
6  */
7
8 package com.ibm.icu.text;
9
10 import java.io.IOException;
11 import java.io.NotSerializableException;
12 import java.io.ObjectInputStream;
13 import java.io.ObjectOutputStream;
14 import java.io.ObjectStreamException;
15 import java.io.Serializable;
16 import java.text.ParseException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Set;
26 import java.util.TreeSet;
27 import java.util.regex.Pattern;
28
29 import com.ibm.icu.impl.PluralRulesLoader;
30 import com.ibm.icu.util.Output;
31 import com.ibm.icu.util.ULocale;
32
33 /**
34  * <p>
35  * Defines rules for mapping non-negative numeric values onto a small set of keywords.
36  * </p>
37  * <p>
38  * Rules are constructed from a text description, consisting of a series of keywords and conditions. The {@link #select}
39  * method examines each condition in order and returns the keyword for the first condition that matches the number. If
40  * none match, {@link #KEYWORD_OTHER} is returned.
41  * </p>
42  * <p>
43  * A PluralRules object is immutable. It contains caches for sample values, but those are synchronized.
44  * <p>
45  * PluralRules is Serializable so that it can be used in formatters, which are serializable.
46  * </p>
47  * <p>
48  * For more information, details, and tips for writing rules, see the <a
49  * href="http://www.unicode.org/draft/reports/tr35/tr35.html#Language_Plural_Rules">LDML spec, C.11 Language Plural
50  * Rules</a>
51  * </p>
52  * <p>
53  * Examples:
54  * </p>
55  * 
56  * <pre>
57  * &quot;one: n is 1; few: n in 2..4&quot;
58  * </pre>
59  * <p>
60  * This defines two rules, for 'one' and 'few'. The condition for 'one' is "n is 1" which means that the number must be
61  * equal to 1 for this condition to pass. The condition for 'few' is "n in 2..4" which means that the number must be
62  * between 2 and 4 inclusive - and be an integer - for this condition to pass. All other numbers are assigned the
63  * keyword "other" by the default rule.
64  * </p>
65  * 
66  * <pre>
67  * &quot;zero: n is 0; one: n is 1; zero: n mod 100 in 1..19&quot;
68  * </pre>
69  * <p>
70  * This illustrates that the same keyword can be defined multiple times. Each rule is examined in order, and the first
71  * keyword whose condition passes is the one returned. Also notes that a modulus is applied to n in the last rule. Thus
72  * its condition holds for 119, 219, 319...
73  * </p>
74  * 
75  * <pre>
76  * &quot;one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14&quot;
77  * </pre>
78  * <p>
79  * This illustrates conjunction and negation. The condition for 'few' has two parts, both of which must be met:
80  * "n mod 10 in 2..4" and "n mod 100 not in 12..14". The first part applies a modulus to n before the test as in the
81  * previous example. The second part applies a different modulus and also uses negation, thus it matches all numbers
82  * _not_ in 12, 13, 14, 112, 113, 114, 212, 213, 214...
83  * </p>
84  * <p>
85  * Syntax:
86  * </p>
87  * <pre>
88  * rules         = rule (';' rule)*
89  * rule          = keyword ':' condition
90  * keyword       = &lt;identifier&gt;
91  * condition     = and_condition ('or' and_condition)*
92  * and_condition = relation ('and' relation)*
93  * relation      = not? expr not? rel not? range_list
94  * expr          = ('n' | 'i' | 'f' | 'v' | 't') (mod value)?
95  * not           = 'not' | '!'
96  * rel           = 'in' | 'is' | '=' | '≠' | 'within'
97  * mod           = 'mod' | '%'
98  * range_list    = (range | value) (',' range_list)*
99  * value         = digit+
100  * digit         = 0|1|2|3|4|5|6|7|8|9
101  * range         = value'..'value
102  * </pre>
103  * <p>Each <b>not</b> term inverts the meaning; however, there should not be more than one of them.</p>
104  * <p>
105  * The i, f, t, and v values are defined as follows:
106  * </p>
107  * <ul>
108  * <li>i to be the integer digits.</li>
109  * <li>f to be the visible decimal digits, as an integer.</li>
110  * <li>t to be the visible decimal digits—without trailing zeros—as an integer.</li>
111  * <li>v to be the number of visible fraction digits.</li>
112  * <li>j is defined to only match integers. That is j is 3 fails if v != 0 (eg for 3.1 or 3.0).</li>
113  * </ul>
114  * <p>
115  * Examples are in the following table:
116  * </p>
117  * <table border='1' style="border-collapse:collapse">
118  * <tbody>
119  * <tr>
120  * <th>n</th>
121  * <th>i</th>
122  * <th>f</th>
123  * <th>v</th>
124  * </tr>
125  * <tr>
126  * <td>1.0</td>
127  * <td>1</td>
128  * <td align="right">0</td>
129  * <td>1</td>
130  * </tr>
131  * <tr>
132  * <td>1.00</td>
133  * <td>1</td>
134  * <td align="right">0</td>
135  * <td>2</td>
136  * </tr>
137  * <tr>
138  * <td>1.3</td>
139  * <td>1</td>
140  * <td align="right">3</td>
141  * <td>1</td>
142  * </tr>
143  * <tr>
144  * <td>1.03</td>
145  * <td>1</td>
146  * <td align="right">3</td>
147  * <td>2</td>
148  * </tr>
149  * <tr>
150  * <td>1.23</td>
151  * <td>1</td>
152  * <td align="right">23</td>
153  * <td>2</td>
154  * </tr>
155  * </tbody>
156  * </table>
157  * <p>
158  * An "identifier" is a sequence of characters that do not have the Unicode Pattern_Syntax or Pattern_White_Space
159  * properties.
160  * <p>
161  * The difference between 'in' and 'within' is that 'in' only includes integers in the specified range, while 'within'
162  * includes all values. Using 'within' with a range_list consisting entirely of values is the same as using 'in' (it's
163  * not an error).
164  * </p>
165  * 
166  * @stable ICU 3.8
167  */
168 public class PluralRules implements Serializable {
169
170     static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze();
171
172     // TODO Remove RulesList by moving its API and fields into PluralRules.
173     /**
174      * @internal
175      * @deprecated This API is ICU internal only.
176      */
177     public static final String CATEGORY_SEPARATOR = ";  ";
178     /**
179      * @internal
180      * @deprecated This API is ICU internal only.
181      */
182     public static final String KEYWORD_RULE_SEPARATOR = ": ";
183
184     private static final long serialVersionUID = 1;
185
186     private final RuleList rules;
187     private final transient Set<String> keywords;
188
189     /**
190      * Provides a factory for returning plural rules
191      * 
192      * @deprecated This API is ICU internal only.
193      * @internal
194      */
195     public static abstract class Factory {
196         /**
197          * Provides access to the predefined <code>PluralRules</code> for a given locale and the plural type.
198          * 
199          * <p>
200          * ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. For these predefined
201          * rules, see CLDR page at http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
202          * 
203          * @param locale
204          *            The locale for which a <code>PluralRules</code> object is returned.
205          * @param type
206          *            The plural type (e.g., cardinal or ordinal).
207          * @return The predefined <code>PluralRules</code> object for this locale. If there's no predefined rules for
208          *         this locale, the rules for the closest parent in the locale hierarchy that has one will be returned.
209          *         The final fallback always returns the default rules.
210          * @deprecated This API is ICU internal only.
211          * @internal
212          */
213         public abstract PluralRules forLocale(ULocale locale, PluralType type);
214
215         /**
216          * Utility for getting CARDINAL rules.
217          * @param locale the locale
218          * @return plural rules.
219          * @deprecated This API is ICU internal only.
220          * @internal
221          */
222         public final PluralRules forLocale(ULocale locale) {
223             return forLocale(locale, PluralType.CARDINAL);
224         }
225
226         /**
227          * Returns the locales for which there is plurals data.
228          * 
229          * @deprecated This API is ICU internal only.
230          * @internal
231          */
232         public abstract ULocale[] getAvailableULocales();
233
234         /**
235          * Returns the 'functionally equivalent' locale with respect to plural rules. Calling PluralRules.forLocale with
236          * the functionally equivalent locale, and with the provided locale, returns rules that behave the same. <br/>
237          * All locales with the same functionally equivalent locale have plural rules that behave the same. This is not
238          * exaustive; there may be other locales whose plural rules behave the same that do not have the same equivalent
239          * locale.
240          * 
241          * @param locale
242          *            the locale to check
243          * @param isAvailable
244          *            if not null and of length > 0, this will hold 'true' at index 0 if locale is directly defined
245          *            (without fallback) as having plural rules
246          * @return the functionally-equivalent locale
247          * @deprecated This API is ICU internal only.
248          * @internal
249          */
250         public abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable);
251
252         /**
253          * Returns the default factory.
254          * @deprecated This API is ICU internal only.
255          * @internal
256          */
257         public static PluralRulesLoader getDefaultFactory() {
258             return PluralRulesLoader.loader;
259         }
260
261         /**
262          * Returns whether or not there are overrides.
263          * @deprecated This API is ICU internal only.
264          * @internal
265          */
266         public abstract boolean hasOverride(ULocale locale);
267     }
268     // Standard keywords.
269
270     /**
271      * Common name for the 'zero' plural form.
272      * @stable ICU 3.8
273      */
274     public static final String KEYWORD_ZERO = "zero";
275
276     /**
277      * Common name for the 'singular' plural form.
278      * @stable ICU 3.8
279      */
280     public static final String KEYWORD_ONE = "one";
281
282     /**
283      * Common name for the 'dual' plural form.
284      * @stable ICU 3.8
285      */
286     public static final String KEYWORD_TWO = "two";
287
288     /**
289      * Common name for the 'paucal' or other special plural form.
290      * @stable ICU 3.8
291      */
292     public static final String KEYWORD_FEW = "few";
293
294     /**
295      * Common name for the arabic (11 to 99) plural form.
296      * @stable ICU 3.8
297      */
298     public static final String KEYWORD_MANY = "many";
299
300     /**
301      * Common name for the default plural form.  This name is returned
302      * for values to which no other form in the rule applies.  It
303      * can additionally be assigned rules of its own.
304      * @stable ICU 3.8
305      */
306     public static final String KEYWORD_OTHER = "other";
307
308     /**
309      * Value returned by {@link #getUniqueKeywordValue} when there is no
310      * unique value to return.
311      * @stable ICU 4.8
312      */
313     public static final double NO_UNIQUE_VALUE = -0.00123456777;
314
315     /**
316      * Type of plurals and PluralRules.
317      * @stable ICU 50
318      */
319     public enum PluralType {
320         /**
321          * Plural rules for cardinal numbers: 1 file vs. 2 files.
322          * @stable ICU 50
323          */
324         CARDINAL,
325         /**
326          * Plural rules for ordinal numbers: 1st file, 2nd file, 3rd file, 4th file, etc.
327          * @stable ICU 50
328          */
329         ORDINAL
330     };
331
332     /*
333      * The default constraint that is always satisfied.
334      */
335     private static final Constraint NO_CONSTRAINT = new Constraint() {
336         private static final long serialVersionUID = 9163464945387899416L;
337
338         public boolean isFulfilled(FixedDecimal n) {
339             return true;
340         }
341
342         public boolean isLimited(SampleType sampleType) {
343             return false;
344         }
345
346         public String toString() {
347             return "";
348         }
349     };
350
351     /**
352      * 
353      */
354     private static final Rule DEFAULT_RULE = new Rule("other", NO_CONSTRAINT, null, null);
355
356     /**
357      * Parses a plural rules description and returns a PluralRules.
358      * @param description the rule description.
359      * @throws ParseException if the description cannot be parsed.
360      *    The exception index is typically not set, it will be -1.
361      * @stable ICU 3.8
362      */
363     public static PluralRules parseDescription(String description)
364             throws ParseException {
365
366         description = description.trim();
367         return description.length() == 0 ? DEFAULT : new PluralRules(parseRuleChain(description));
368     }
369
370     /**
371      * Creates a PluralRules from a description if it is parsable,
372      * otherwise returns null.
373      * @param description the rule description.
374      * @return the PluralRules
375      * @stable ICU 3.8
376      */
377     public static PluralRules createRules(String description) {
378         try {
379             return parseDescription(description);
380         } catch(Exception e) {
381             return null;
382         }
383     }
384
385     /**
386      * The default rules that accept any number and return
387      * {@link #KEYWORD_OTHER}.
388      * @stable ICU 3.8
389      */
390     public static final PluralRules DEFAULT = new PluralRules(new RuleList().addRule(DEFAULT_RULE));
391
392     private enum Operand {
393         n,
394         i,
395         f,
396         t,
397         v,
398         w,
399         /**@deprecated*/
400         j;
401     }
402
403     /**
404      * @internal
405      * @deprecated This API is ICU internal only.
406      */
407     public static class FixedDecimal extends Number implements Comparable<FixedDecimal> {
408         private static final long serialVersionUID = -4756200506571685661L;
409         /**
410          * @internal
411          * @deprecated This API is ICU internal only.
412          */
413         public final double source;
414         /**
415          * @internal
416          * @deprecated This API is ICU internal only.
417          */
418         public final int visibleDecimalDigitCount;
419         /**
420          * @internal
421          * @deprecated This API is ICU internal only.
422          */
423         public final int visibleDecimalDigitCountWithoutTrailingZeros;
424         /**
425          * @internal
426          * @deprecated This API is ICU internal only.
427          */
428         public final long decimalDigits;
429         /**
430          * @internal
431          * @deprecated This API is ICU internal only.
432          */
433         public final long decimalDigitsWithoutTrailingZeros;
434         /**
435          * @internal
436          * @deprecated This API is ICU internal only.
437          */
438         public final long integerValue;
439         /**
440          * @internal
441          * @deprecated This API is ICU internal only.
442          */
443         public final boolean hasIntegerValue;
444         /**
445          * @internal
446          * @deprecated This API is ICU internal only.
447          */
448         public final boolean isNegative;
449         private final int baseFactor;
450
451         /**
452          * @internal
453          * @deprecated This API is ICU internal only.
454          */
455         public double getSource() {
456             return source;
457         }
458
459         /**
460          * @internal
461          * @deprecated This API is ICU internal only.
462          */
463         public int getVisibleDecimalDigitCount() {
464             return visibleDecimalDigitCount;
465         }
466
467         /**
468          * @internal
469          * @deprecated This API is ICU internal only.
470          */
471         public int getVisibleDecimalDigitCountWithoutTrailingZeros() {
472             return visibleDecimalDigitCountWithoutTrailingZeros;
473         }
474
475         /**
476          * @internal
477          * @deprecated This API is ICU internal only.
478          */
479         public long getDecimalDigits() {
480             return decimalDigits;
481         }
482
483         /**
484          * @internal
485          * @deprecated This API is ICU internal only.
486          */
487         public long getDecimalDigitsWithoutTrailingZeros() {
488             return decimalDigitsWithoutTrailingZeros;
489         }
490
491         /**
492          * @internal
493          * @deprecated This API is ICU internal only.
494          */
495         public long getIntegerValue() {
496             return integerValue;
497         }
498
499         /**
500          * @internal
501          * @deprecated This API is ICU internal only.
502          */
503         public boolean isHasIntegerValue() {
504             return hasIntegerValue;
505         }
506
507         /**
508          * @internal
509          * @deprecated This API is ICU internal only.
510          */
511         public boolean isNegative() {
512             return isNegative;
513         }
514
515         /**
516          * @internal
517          * @deprecated This API is ICU internal only.
518          */
519         public int getBaseFactor() {
520             return baseFactor;
521         }
522
523         /**
524          * @internal
525          * @deprecated This API is ICU internal only.
526          * @param n is the original number
527          * @param v number of digits to the right of the decimal place. e.g 1.00 = 2 25. = 0
528          * @param f Corresponds to f in the plural rules grammar.
529          *   The digits to the right of the decimal place as an integer. e.g 1.10 = 10
530          */
531         public FixedDecimal(double n, int v, long f) {
532             isNegative = n < 0;
533             source = isNegative ? -n : n;
534             visibleDecimalDigitCount = v;
535             decimalDigits = f;
536             integerValue = (long)n;
537             hasIntegerValue = source == integerValue;
538             // check values. TODO make into unit test.
539             //            
540             //            long visiblePower = (int) Math.pow(10, v);
541             //            if (fractionalDigits > visiblePower) {
542             //                throw new IllegalArgumentException();
543             //            }
544             //            double fraction = intValue + (fractionalDigits / (double) visiblePower);
545             //            if (fraction != source) {
546             //                double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
547             //                if (diff > 0.00000001d) {
548             //                    throw new IllegalArgumentException();
549             //                }
550             //            }
551             if (f == 0) {
552                 decimalDigitsWithoutTrailingZeros = 0;
553                 visibleDecimalDigitCountWithoutTrailingZeros = 0;
554             } else {
555                 long fdwtz = f;
556                 int trimmedCount = v;
557                 while ((fdwtz%10) == 0) {
558                     fdwtz /= 10;
559                     --trimmedCount;
560                 }
561                 decimalDigitsWithoutTrailingZeros = fdwtz;
562                 visibleDecimalDigitCountWithoutTrailingZeros = trimmedCount;
563             }
564             baseFactor = (int) Math.pow(10, v);
565         }
566
567         /**
568          * @internal
569          * @deprecated This API is ICU internal only.
570          */
571         public FixedDecimal(double n, int v) {
572             this(n,v,getFractionalDigits(n, v));
573         }
574
575         private static int getFractionalDigits(double n, int v) {
576             if (v == 0) {
577                 return 0;
578             } else {
579                 int baseFactor = (int) Math.pow(10, v);
580                 long scaled = Math.round(n * baseFactor);
581                 return (int) (scaled % baseFactor);
582             }
583         }
584
585         /**
586          * @internal
587          * @deprecated This API is ICU internal only.
588          */
589         public FixedDecimal(double n) {
590             this(n, decimals(n));
591         }
592
593         /**
594          * @internal
595          * @deprecated This API is ICU internal only.
596          */
597         public FixedDecimal(long n) {
598             this(n,0);
599         }
600
601         /**
602          * @internal
603          * @deprecated This API is ICU internal only.
604          */
605         public static int decimals(double n) {
606             // Ugly...
607             String temp = String.valueOf(n);
608             return temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1;
609         }
610
611         /**
612          * @internal
613          * @deprecated This API is ICU internal only.
614          */
615         public FixedDecimal (String n) {
616             // Ugly, but for samples we don't care.
617             this(Double.parseDouble(n), getVisibleFractionCount(n));
618         }
619
620         private static int getVisibleFractionCount(String value) {
621             value = value.trim();
622             int decimalPos = value.indexOf('.') + 1;
623             if (decimalPos == 0) {
624                 return 0;
625             } else {
626                 return value.length() - decimalPos;
627             }
628         }
629
630         /**
631          * @internal
632          * @deprecated This API is ICU internal only.
633          */
634         public double get(Operand operand) {
635             switch(operand) {
636             default: return source;
637             case i: return integerValue;
638             case f: return decimalDigits;
639             case t: return decimalDigitsWithoutTrailingZeros;
640             case v: return visibleDecimalDigitCount;
641             case w: return visibleDecimalDigitCountWithoutTrailingZeros;
642             }
643         }
644
645         /**
646          * @internal
647          * @deprecated This API is ICU internal only.
648          */
649         public static Operand getOperand(String t) {
650             return Operand.valueOf(t);
651         }
652
653         /**
654          * We're not going to care about NaN.
655          * @internal
656          * @deprecated This API is ICU internal only.
657          */
658         public int compareTo(FixedDecimal other) {
659             if (integerValue != other.integerValue) {
660                 return integerValue < other.integerValue ? -1 : 1;
661             }
662             if (source != other.source) {
663                 return source < other.source ? -1 : 1;
664             }
665             if (visibleDecimalDigitCount != other.visibleDecimalDigitCount) {
666                 return visibleDecimalDigitCount < other.visibleDecimalDigitCount ? -1 : 1;
667             }
668             long diff = decimalDigits - other.decimalDigits;
669             if (diff != 0) {
670                 return diff < 0 ? -1 : 1;
671             }
672             return 0;
673         }
674
675         /**
676          * @internal
677          * @deprecated This API is ICU internal only.
678          */
679         @Override
680         public boolean equals(Object arg0) {
681             if (arg0 == null) {
682                 return false;
683             }
684             if (arg0 == this) {
685                 return true;
686             }
687             if (!(arg0 instanceof FixedDecimal)) {
688                 return false;
689             }
690             FixedDecimal other = (FixedDecimal)arg0;
691             return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount && decimalDigits == other.decimalDigits;
692         }
693
694         /**
695          * @internal
696          * @deprecated This API is ICU internal only.
697          */
698         @Override
699         public int hashCode() {
700             // TODO Auto-generated method stub
701             return (int)(decimalDigits + 37 * (visibleDecimalDigitCount + (int)(37 * source)));
702         }
703
704         /**
705          * @internal
706          * @deprecated This API is ICU internal only.
707          */
708         @Override
709         public String toString() {
710             return String.format("%." + visibleDecimalDigitCount + "f", source);
711         }
712
713         /**
714          * @internal
715          * @deprecated This API is ICU internal only.
716          */
717         public boolean hasIntegerValue() {
718             return hasIntegerValue;
719         }
720
721         /**
722          * @internal
723          * @deprecated This API is ICU internal only.
724          */
725         @Override
726         public int intValue() {
727             // TODO Auto-generated method stub
728             return (int)integerValue;
729         }
730
731         /**
732          * @internal
733          * @deprecated This API is ICU internal only.
734          */
735         @Override
736         public long longValue() {
737             return integerValue;
738         }
739
740         /**
741          * @internal
742          * @deprecated This API is ICU internal only.
743          */
744         @Override
745         public float floatValue() {
746             return (float) source;
747         }
748
749         /**
750          * @internal
751          * @deprecated This API is ICU internal only.
752          */
753         @Override
754         public double doubleValue() {
755             return source;
756         }
757
758         /**
759          * @internal
760          * @deprecated This API is ICU internal only.
761          */
762         public long getShiftedValue() {
763             return integerValue * baseFactor + decimalDigits;
764         }
765
766         private void writeObject(
767                 ObjectOutputStream out)
768                         throws IOException {
769             throw new NotSerializableException();
770         }
771
772         private void readObject(ObjectInputStream in
773                 ) throws IOException, ClassNotFoundException {
774             throw new NotSerializableException();
775         }
776     }
777
778     /**
779      * Selection parameter for either integer-only or decimal-only.
780      * @internal
781      * @deprecated This API is ICU internal only.
782      */
783     public enum SampleType {INTEGER, DECIMAL}
784
785     /**
786      * A range of NumberInfo that includes all values with the same visibleFractionDigitCount.
787      * @internal
788      * @deprecated This API is ICU internal only.
789      */
790     public static class FixedDecimalRange {
791         /**
792          * @internal
793          * @deprecated This API is ICU internal only.
794          */
795         public final FixedDecimal start;
796         /**
797          * @internal
798          * @deprecated This API is ICU internal only.
799          */
800         public final FixedDecimal end;
801         /**
802          * @internal
803          * @deprecated This API is ICU internal only.
804          */
805         public FixedDecimalRange(FixedDecimal start, FixedDecimal end) {
806             if (start.visibleDecimalDigitCount != end.visibleDecimalDigitCount) {
807                 throw new IllegalArgumentException("Ranges must have the same number of visible decimals: " + start + "~" + end);
808             }
809             this.start = start;
810             this.end = end;
811         }
812         /**
813          * @internal
814          * @deprecated This API is ICU internal only.
815          */
816         @Override
817         public String toString() {
818             return start + (end == start ? "" : "~" + end);
819         }
820     }
821
822     /**
823      * A list of NumberInfo that includes all values with the same visibleFractionDigitCount.
824      * @internal
825      * @deprecated This API is ICU internal only.
826      */
827     public static class FixedDecimalSamples {
828         /**
829          * @internal
830          * @deprecated This API is ICU internal only.
831          */
832         public final SampleType sampleType;
833         /**
834          * @internal
835          * @deprecated This API is ICU internal only.
836          */
837         public final Set<FixedDecimalRange> samples;
838         /**
839          * @internal
840          * @deprecated This API is ICU internal only.
841          */
842         public final boolean bounded;
843         /**
844          * The samples must be immutable.
845          * @param sampleType
846          * @param samples
847          */
848         private FixedDecimalSamples(SampleType sampleType, Set<FixedDecimalRange> samples, boolean bounded) {
849             super();
850             this.sampleType = sampleType;
851             this.samples = samples;
852             this.bounded = bounded;
853         }
854         /*
855          * Parse a list of the form described in CLDR. The source must be trimmed.
856          */
857         static FixedDecimalSamples parse(String source) {
858             SampleType sampleType2;
859             boolean bounded2 = true;
860             boolean haveBound = false;
861             Set<FixedDecimalRange> samples2 = new LinkedHashSet<FixedDecimalRange>();
862
863             if (source.startsWith("integer")) {
864                 sampleType2 = SampleType.INTEGER;
865             } else if (source.startsWith("decimal")) {
866                 sampleType2 = SampleType.DECIMAL;
867             } else {
868                 throw new IllegalArgumentException("Samples must start with 'integer' or 'decimal'");
869             }
870             source = source.substring(7).trim(); // remove both
871
872             for (String range : COMMA_SEPARATED.split(source)) {
873                 if (range.equals("…") || range.equals("...")) {
874                     bounded2 = false;
875                     haveBound = true;
876                     continue;
877                 }
878                 if (haveBound) {
879                     throw new IllegalArgumentException("Can only have … at the end of samples: " + range);
880                 }
881                 String[] rangeParts = TILDE_SEPARATED.split(range);
882                 switch (rangeParts.length) {
883                 case 1: 
884                     FixedDecimal sample = new FixedDecimal(rangeParts[0]);
885                     checkDecimal(sampleType2, sample);
886                     samples2.add(new FixedDecimalRange(sample, sample));
887                     break;
888                 case 2:
889                     FixedDecimal start = new FixedDecimal(rangeParts[0]);
890                     FixedDecimal end = new FixedDecimal(rangeParts[1]);
891                     checkDecimal(sampleType2, start);
892                     checkDecimal(sampleType2, end);
893                     samples2.add(new FixedDecimalRange(start, end));
894                     break;
895                 default: throw new IllegalArgumentException("Ill-formed number range: " + range);
896                 }
897             }
898             return new FixedDecimalSamples(sampleType2, Collections.unmodifiableSet(samples2), bounded2);
899         }
900
901         private static void checkDecimal(SampleType sampleType2, FixedDecimal sample) {
902             if ((sampleType2 == SampleType.INTEGER) != (sample.getVisibleDecimalDigitCount() == 0)) {
903                 throw new IllegalArgumentException("Ill-formed number range: " + sample);    
904             }
905         }
906
907         /**
908          * @internal
909          * @deprecated This API is ICU internal only.
910          */
911         public Set<Double> addSamples(Set<Double> result) {
912             for (FixedDecimalRange item : samples) {
913                 // we have to convert to longs so we don't get strange double issues
914                 long startDouble = item.start.getShiftedValue();
915                 long endDouble = item.end.getShiftedValue();
916
917                 for (long d = startDouble; d <= endDouble; d += 1) {
918                     result.add(d/(double)item.start.baseFactor);
919                 }
920             }
921             return result;
922         }
923
924         /**
925          * @internal
926          * @deprecated This API is ICU internal only.
927          */
928         @Override
929         public String toString() {
930             StringBuilder b = new StringBuilder("@").append(sampleType.toString().toLowerCase(Locale.ENGLISH));
931             boolean first = true;
932             for (FixedDecimalRange item : samples) {
933                 if (first) {
934                     first = false;
935                 } else {
936                     b.append(",");
937                 }
938                 b.append(' ').append(item);
939             }
940             if (!bounded) {
941                 b.append(", …");
942             }
943             return b.toString();
944         }
945
946         /**
947          * @internal
948          * @deprecated This API is ICU internal only.
949          */
950         public Set<FixedDecimalRange> getSamples() {
951             return samples;
952         }
953
954         /**
955          * @internal
956          * @deprecated This API is ICU internal only.
957          */
958         public void getStartEndSamples(Set<FixedDecimal> target) {
959             for (FixedDecimalRange item : samples) {
960                 target.add(item.start);
961                 target.add(item.end);
962             }
963         }
964     }
965
966     /*
967      * A constraint on a number.
968      */
969     private interface Constraint extends Serializable {
970         /*
971          * Returns true if the number fulfills the constraint.
972          * @param n the number to test, >= 0.
973          */
974         boolean isFulfilled(FixedDecimal n);
975
976         /*
977          * Returns false if an unlimited number of values fulfills the
978          * constraint.
979          */
980         boolean isLimited(SampleType sampleType);
981     }
982
983     static class SimpleTokenizer {
984         static final UnicodeSet BREAK_AND_IGNORE = new UnicodeSet(0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x20).freeze();
985         static final UnicodeSet BREAK_AND_KEEP = new UnicodeSet('!', '!', '%', '%', ',', ',', '.', '.', '=', '=').freeze();
986         static String[] split(String source) {
987             int last = -1;
988             List<String> result = new ArrayList<String>();
989             for (int i = 0; i < source.length(); ++i) {
990                 char ch = source.charAt(i);
991                 if (BREAK_AND_IGNORE.contains(ch)) {
992                     if (last >= 0) {
993                         result.add(source.substring(last,i));
994                         last = -1;
995                     }
996                 } else if (BREAK_AND_KEEP.contains(ch)) {
997                     if (last >= 0) {
998                         result.add(source.substring(last,i));
999                     }
1000                     result.add(source.substring(i,i+1));
1001                     last = -1;
1002                 } else if (last < 0) {
1003                     last = i;
1004                 }
1005             }
1006             if (last >= 0) {
1007                 result.add(source.substring(last));
1008             }
1009             return result.toArray(new String[result.size()]);
1010         }
1011     }
1012
1013     /*
1014      * syntax:
1015      * condition :       or_condition
1016      *                   and_condition
1017      * or_condition :    and_condition 'or' condition
1018      * and_condition :   relation
1019      *                   relation 'and' relation
1020      * relation :        in_relation
1021      *                   within_relation
1022      * in_relation :     not? expr not? in not? range
1023      * within_relation : not? expr not? 'within' not? range
1024      * not :             'not'
1025      *                   '!'
1026      * expr :            'n'
1027      *                   'n' mod value
1028      * mod :             'mod'
1029      *                   '%'
1030      * in :              'in'
1031      *                   'is'
1032      *                   '='
1033      *                   '≠'
1034      * value :           digit+
1035      * digit :           0|1|2|3|4|5|6|7|8|9
1036      * range :           value'..'value
1037      */
1038     private static Constraint parseConstraint(String description)
1039             throws ParseException {
1040
1041         Constraint result = null;
1042         String[] or_together = OR_SEPARATED.split(description);
1043         for (int i = 0; i < or_together.length; ++i) {
1044             Constraint andConstraint = null;
1045             String[] and_together = AND_SEPARATED.split(or_together[i]);
1046             for (int j = 0; j < and_together.length; ++j) {
1047                 Constraint newConstraint = NO_CONSTRAINT;
1048
1049                 String condition = and_together[j].trim();
1050                 String[] tokens = SimpleTokenizer.split(condition);
1051
1052                 int mod = 0;
1053                 boolean inRange = true;
1054                 boolean integersOnly = true;
1055                 double lowBound = Long.MAX_VALUE;
1056                 double highBound = Long.MIN_VALUE;
1057                 long[] vals = null;
1058
1059                 int x = 0;
1060                 String t = tokens[x++];
1061                 boolean hackForCompatibility = false;
1062                 Operand operand;
1063                 try {
1064                     operand = FixedDecimal.getOperand(t);
1065                 } catch (Exception e) {
1066                     throw unexpected(t, condition);
1067                 }
1068                 if (x < tokens.length) {
1069                     t = tokens[x++];
1070                     if ("mod".equals(t) || "%".equals(t)) {
1071                         mod = Integer.parseInt(tokens[x++]);
1072                         t = nextToken(tokens, x++, condition);
1073                     }
1074                     if ("not".equals(t)) {
1075                         inRange = !inRange;
1076                         t = nextToken(tokens, x++, condition);
1077                         if ("=".equals(t)) {
1078                             throw unexpected(t, condition);
1079                         }
1080                     } else if ("!".equals(t)) {
1081                         inRange = !inRange;
1082                         t = nextToken(tokens, x++, condition);
1083                         if (!"=".equals(t)) {
1084                             throw unexpected(t, condition);
1085                         }
1086                     }
1087                     if ("is".equals(t) || "in".equals(t) || "=".equals(t)) {
1088                         hackForCompatibility = "is".equals(t);
1089                         if (hackForCompatibility && !inRange) {
1090                             throw unexpected(t, condition);
1091                         }
1092                         t = nextToken(tokens, x++, condition);
1093                     } else if ("within".equals(t)) {
1094                         integersOnly = false;
1095                         t = nextToken(tokens, x++, condition);
1096                     } else {
1097                         throw unexpected(t, condition);
1098                     }
1099                     if ("not".equals(t)) {
1100                         if (!hackForCompatibility && !inRange) {
1101                             throw unexpected(t, condition);
1102                         }
1103                         inRange = !inRange;
1104                         t = nextToken(tokens, x++, condition);
1105                     }
1106
1107                     List<Long> valueList = new ArrayList<Long>();
1108
1109                     // the token t is always one item ahead
1110                     while (true) {
1111                         long low = Long.parseLong(t);
1112                         long high = low;
1113                         if (x < tokens.length) {
1114                             t = nextToken(tokens, x++, condition);
1115                             if (t.equals(".")) {
1116                                 t = nextToken(tokens, x++, condition);
1117                                 if (!t.equals(".")) {
1118                                     throw unexpected(t, condition);
1119                                 }
1120                                 t = nextToken(tokens, x++, condition);
1121                                 high = Long.parseLong(t);
1122                                 if (x < tokens.length) {
1123                                     t = nextToken(tokens, x++, condition);
1124                                     if (!t.equals(",")) { // adjacent number: 1 2
1125                                         // no separator, fail
1126                                         throw unexpected(t, condition);
1127                                     }                                
1128                                 }
1129                             } else if (!t.equals(",")) { // adjacent number: 1 2
1130                                 // no separator, fail
1131                                 throw unexpected(t, condition);
1132                             }
1133                         }
1134                         // at this point, either we are out of tokens, or t is ','
1135                         if (low > high) {
1136                             throw unexpected(low + "~" + high, condition);
1137                         } else if (mod != 0 && high >= mod) {
1138                             throw unexpected(high + ">mod=" + mod, condition);
1139                         }
1140                         valueList.add(low);
1141                         valueList.add(high);
1142                         lowBound = Math.min(lowBound, low);
1143                         highBound = Math.max(highBound, high);
1144                         if (x >= tokens.length) {
1145                             break;
1146                         }
1147                         t = nextToken(tokens, x++, condition);
1148                     }
1149
1150                     if (t.equals(",")) {
1151                         throw unexpected(t, condition);
1152                     }
1153
1154                     if (valueList.size() == 2) {
1155                         vals = null;
1156                     } else {
1157                         vals = new long[valueList.size()];
1158                         for (int k = 0; k < vals.length; ++k) {
1159                             vals[k] = valueList.get(k);
1160                         }
1161                     }
1162
1163                     // Hack to exclude "is not 1,2"
1164                     if (lowBound != highBound && hackForCompatibility && !inRange) {
1165                         throw unexpected("is not <range>", condition);
1166                     }
1167
1168                     newConstraint =
1169                             new RangeConstraint(mod, inRange, operand, integersOnly, lowBound, highBound, vals);
1170                 }
1171
1172                 if (andConstraint == null) {
1173                     andConstraint = newConstraint;
1174                 } else {
1175                     andConstraint = new AndConstraint(andConstraint,
1176                             newConstraint);
1177                 }
1178             }
1179
1180             if (result == null) {
1181                 result = andConstraint;
1182             } else {
1183                 result = new OrConstraint(result, andConstraint);
1184             }
1185         }
1186         return result;
1187     }
1188
1189     static final Pattern AT_SEPARATED = Pattern.compile("\\s*\\Q\\E@\\s*");
1190     static final Pattern OR_SEPARATED = Pattern.compile("\\s*or\\s*");
1191     static final Pattern AND_SEPARATED = Pattern.compile("\\s*and\\s*");
1192     static final Pattern COMMA_SEPARATED = Pattern.compile("\\s*,\\s*");
1193     static final Pattern DOTDOT_SEPARATED = Pattern.compile("\\s*\\Q..\\E\\s*");
1194     static final Pattern TILDE_SEPARATED = Pattern.compile("\\s*~\\s*");
1195     static final Pattern SEMI_SEPARATED = Pattern.compile("\\s*;\\s*");
1196
1197
1198     /* Returns a parse exception wrapping the token and context strings. */
1199     private static ParseException unexpected(String token, String context) {
1200         return new ParseException("unexpected token '" + token +
1201                 "' in '" + context + "'", -1);
1202     }
1203
1204     /*
1205      * Returns the token at x if available, else throws a parse exception.
1206      */
1207     private static String nextToken(String[] tokens, int x, String context)
1208             throws ParseException {
1209         if (x < tokens.length) {
1210             return tokens[x];
1211         }
1212         throw new ParseException("missing token at end of '" + context + "'", -1);
1213     }
1214
1215     /*
1216      * Syntax:
1217      * rule : keyword ':' condition
1218      * keyword: <identifier>
1219      */
1220     private static Rule parseRule(String description) throws ParseException {
1221         if (description.length() == 0) {
1222             return DEFAULT_RULE;
1223         }
1224
1225         description = description.toLowerCase(Locale.ENGLISH);
1226
1227         int x = description.indexOf(':');
1228         if (x == -1) {
1229             throw new ParseException("missing ':' in rule description '" +
1230                     description + "'", 0);
1231         }
1232
1233         String keyword = description.substring(0, x).trim();
1234         if (!isValidKeyword(keyword)) {
1235             throw new ParseException("keyword '" + keyword +
1236                     " is not valid", 0);
1237         }
1238
1239         description = description.substring(x+1).trim();
1240         String[] constraintOrSamples = AT_SEPARATED.split(description);
1241         boolean sampleFailure = false;
1242         FixedDecimalSamples integerSamples = null, decimalSamples = null;
1243         switch (constraintOrSamples.length) {
1244         case 1: break;
1245         case 2: 
1246             integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
1247             if (integerSamples.sampleType == SampleType.DECIMAL) {
1248                 decimalSamples = integerSamples;
1249                 integerSamples = null;
1250             }
1251             break;
1252         case 3:
1253             integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
1254             decimalSamples = FixedDecimalSamples.parse(constraintOrSamples[2]);
1255             if (integerSamples.sampleType != SampleType.INTEGER || decimalSamples.sampleType != SampleType.DECIMAL) {
1256                 throw new IllegalArgumentException("Must have @integer then @decimal in " + description);
1257             }
1258             break;
1259         default: 
1260             throw new IllegalArgumentException("Too many samples in " + description);
1261         }
1262         if (sampleFailure) {
1263             throw new IllegalArgumentException("Ill-formed samples—'@' characters.");
1264         }
1265
1266         // 'other' is special, and must have no rules; all other keywords must have rules.
1267         boolean isOther = keyword.equals("other");
1268         if (isOther != (constraintOrSamples[0].length() == 0)) {
1269             throw new IllegalArgumentException("The keyword 'other' must have no constraints, just samples.");
1270         }
1271
1272         Constraint constraint;
1273         if (isOther) {
1274             constraint = NO_CONSTRAINT;
1275         } else {
1276             constraint = parseConstraint(constraintOrSamples[0]);
1277         }
1278         return new Rule(keyword, constraint, integerSamples, decimalSamples);
1279     }
1280
1281
1282     /*
1283      * Syntax:
1284      * rules : rule
1285      *         rule ';' rules
1286      */
1287     private static RuleList parseRuleChain(String description)
1288             throws ParseException {
1289         RuleList result = new RuleList();
1290         // remove trailing ;
1291         if (description.endsWith(";")) { 
1292             description = description.substring(0,description.length()-1);
1293         }
1294         String[] rules = SEMI_SEPARATED.split(description);
1295         for (int i = 0; i < rules.length; ++i) {
1296             Rule rule = parseRule(rules[i].trim());
1297             result.hasExplicitBoundingInfo |= rule.integerSamples != null || rule.decimalSamples != null;
1298             result.addRule(rule);
1299         }
1300         return result.finish();
1301     }
1302
1303     /*
1304      * An implementation of Constraint representing a modulus,
1305      * a range of values, and include/exclude. Provides lots of
1306      * convenience factory methods.
1307      */
1308     private static class RangeConstraint implements Constraint, Serializable {
1309         private static final long serialVersionUID = 1;
1310
1311         private final int mod;
1312         private final boolean inRange;
1313         private final boolean integersOnly;
1314         private final double lowerBound;
1315         private final double upperBound;
1316         private final long[] range_list;
1317         private final Operand operand;
1318
1319         RangeConstraint(int mod, boolean inRange, Operand operand, boolean integersOnly,
1320                 double lowBound, double highBound, long[] vals) {
1321             this.mod = mod;
1322             this.inRange = inRange;
1323             this.integersOnly = integersOnly;
1324             this.lowerBound = lowBound;
1325             this.upperBound = highBound;
1326             this.range_list = vals;
1327             this.operand = operand;
1328         }
1329
1330         public boolean isFulfilled(FixedDecimal number) {
1331             double n = number.get(operand);
1332             if ((integersOnly && (n - (long)n) != 0.0
1333                     || operand == Operand.j && number.visibleDecimalDigitCount != 0)) {
1334                 return !inRange;
1335             }
1336             if (mod != 0) {
1337                 n = n % mod;    // java % handles double numerator the way we want
1338             }
1339             boolean test = n >= lowerBound && n <= upperBound;
1340             if (test && range_list != null) {
1341                 test = false;
1342                 for (int i = 0; !test && i < range_list.length; i += 2) {
1343                     test = n >= range_list[i] && n <= range_list[i+1];
1344                 }
1345             }
1346             return inRange == test;
1347         }
1348
1349         public boolean isLimited(SampleType sampleType) {
1350             boolean valueIsZero = lowerBound == upperBound && lowerBound == 0d;
1351             boolean hasDecimals = 
1352                     (operand == Operand.v || operand == Operand.w || operand == Operand.f || operand == Operand.t)
1353                     && inRange != valueIsZero; // either NOT f = zero or f = non-zero
1354             switch (sampleType) {
1355             case INTEGER: 
1356                 return hasDecimals // will be empty
1357                         || (operand == Operand.n || operand == Operand.i || operand == Operand.j)
1358                         && mod == 0 
1359                         && inRange;
1360
1361             case DECIMAL:
1362                 return  (!hasDecimals || operand == Operand.n || operand == Operand.j)
1363                         && (integersOnly || lowerBound == upperBound)
1364                         && mod == 0 
1365                         && inRange;
1366             }
1367             return false;
1368         }
1369
1370         public String toString() {
1371             StringBuilder result = new StringBuilder();
1372             result.append(operand);
1373             if (mod != 0) {
1374                 result.append(" % ").append(mod);
1375             }
1376             boolean isList = lowerBound != upperBound;
1377             result.append(
1378                     !isList ? (inRange ? " = " : " != ")
1379                             : integersOnly ? (inRange ? " = " : " != ")
1380                                     : (inRange ? " within " : " not within ") 
1381                     );
1382             if (range_list != null) {
1383                 for (int i = 0; i < range_list.length; i += 2) {
1384                     addRange(result, range_list[i], range_list[i+1], i != 0);
1385                 }
1386             } else {
1387                 addRange(result, lowerBound, upperBound, false);
1388             }
1389             return result.toString();
1390         }
1391     }
1392
1393     private static void addRange(StringBuilder result, double lb, double ub, boolean addSeparator) {
1394         if (addSeparator) {
1395             result.append(",");
1396         }
1397         if (lb == ub) {
1398             result.append(format(lb));
1399         } else {
1400             result.append(format(lb) + ".." + format(ub));
1401         }
1402     }
1403
1404     private static String format(double lb) {
1405         long lbi = (long) lb;
1406         return lb == lbi ? String.valueOf(lbi) : String.valueOf(lb);
1407     }
1408
1409     /* Convenience base class for and/or constraints. */
1410     private static abstract class BinaryConstraint implements Constraint,
1411     Serializable {
1412         private static final long serialVersionUID = 1;
1413         protected final Constraint a;
1414         protected final Constraint b;
1415
1416         protected BinaryConstraint(Constraint a, Constraint b) {
1417             this.a = a;
1418             this.b = b;
1419         }
1420     }
1421
1422     /* A constraint representing the logical and of two constraints. */
1423     private static class AndConstraint extends BinaryConstraint {
1424         private static final long serialVersionUID = 7766999779862263523L;
1425
1426         AndConstraint(Constraint a, Constraint b) {
1427             super(a, b);
1428         }
1429
1430         public boolean isFulfilled(FixedDecimal n) {
1431             return a.isFulfilled(n) 
1432                     && b.isFulfilled(n);
1433         }
1434
1435         public boolean isLimited(SampleType sampleType) {
1436             // we ignore the case where both a and b are unlimited but no values
1437             // satisfy both-- we still consider this 'unlimited'
1438             return a.isLimited(sampleType) 
1439                     || b.isLimited(sampleType);
1440         }
1441
1442         public String toString() {
1443             return a.toString() + " and " + b.toString();
1444         }
1445     }
1446
1447     /* A constraint representing the logical or of two constraints. */
1448     private static class OrConstraint extends BinaryConstraint {
1449         private static final long serialVersionUID = 1405488568664762222L;
1450
1451         OrConstraint(Constraint a, Constraint b) {
1452             super(a, b);
1453         }
1454
1455         public boolean isFulfilled(FixedDecimal n) {
1456             return a.isFulfilled(n) 
1457                     || b.isFulfilled(n);
1458         }
1459
1460         public boolean isLimited(SampleType sampleType) {
1461             return a.isLimited(sampleType) 
1462                     && b.isLimited(sampleType);
1463         }
1464
1465         public String toString() {
1466             return a.toString() + " or " + b.toString();
1467         }
1468     }
1469
1470     /*
1471      * Implementation of Rule that uses a constraint.
1472      * Provides 'and' and 'or' to combine constraints.  Immutable.
1473      */
1474     private static class Rule implements Serializable {
1475         private static final long serialVersionUID = 1;
1476         private final String keyword;
1477         private final Constraint constraint;
1478         private final FixedDecimalSamples integerSamples;
1479         private final FixedDecimalSamples decimalSamples;
1480
1481         public Rule(String keyword, Constraint constraint, FixedDecimalSamples integerSamples, FixedDecimalSamples decimalSamples) {
1482             this.keyword = keyword;
1483             this.constraint = constraint;
1484             this.integerSamples = integerSamples;
1485             this.decimalSamples = decimalSamples;
1486         }
1487
1488         @SuppressWarnings("unused")
1489         public Rule and(Constraint c) {
1490             return new Rule(keyword, new AndConstraint(constraint, c), integerSamples, decimalSamples);
1491         }
1492
1493         @SuppressWarnings("unused")
1494         public Rule or(Constraint c) {
1495             return new Rule(keyword, new OrConstraint(constraint, c), integerSamples, decimalSamples);
1496         }
1497
1498         public String getKeyword() {
1499             return keyword;
1500         }
1501
1502         public boolean appliesTo(FixedDecimal n) {
1503             return constraint.isFulfilled(n);
1504         }
1505
1506         public boolean isLimited(SampleType sampleType) {
1507             return constraint.isLimited(sampleType);
1508         }
1509
1510         public String toString() {
1511             return keyword + ": " + constraint.toString() 
1512                     + (integerSamples == null ? "" : " " + integerSamples.toString())
1513                     + (decimalSamples == null ? "" : " " + decimalSamples.toString());
1514         }
1515
1516         /**
1517          * @internal
1518          * @deprecated This API is ICU internal only.
1519          */
1520         @Override
1521         public int hashCode() {
1522             return keyword.hashCode() ^ constraint.hashCode();
1523         }
1524
1525         public String getConstraint() {
1526             return constraint.toString();
1527         }
1528     }
1529
1530     private static class RuleList implements Serializable {
1531         private boolean hasExplicitBoundingInfo = false;
1532         private static final long serialVersionUID = 1;
1533         private final List<Rule> rules = new ArrayList<Rule>();
1534
1535         public RuleList addRule(Rule nextRule) {
1536             String keyword = nextRule.getKeyword();
1537             for (Rule rule : rules) {
1538                 if (keyword.equals(rule.getKeyword())) {
1539                     throw new IllegalArgumentException("Duplicate keyword: " + keyword);
1540                 }
1541             }
1542             rules.add(nextRule);
1543             return this;
1544         }
1545
1546         public RuleList finish() throws ParseException {
1547             // make sure that 'other' is present, and at the end.
1548             Rule otherRule = null;
1549             for (Iterator<Rule> it = rules.iterator(); it.hasNext();) {
1550                 Rule rule = it.next();
1551                 if ("other".equals(rule.getKeyword())) {
1552                     otherRule = rule;
1553                     it.remove();
1554                 }
1555             }
1556             if (otherRule == null) {
1557                 otherRule = parseRule("other:"); // make sure we have always have an 'other' a rule
1558             }
1559             rules.add(otherRule);
1560             return this;
1561         }
1562
1563         private Rule selectRule(FixedDecimal n) {
1564             for (Rule rule : rules) {
1565                 if (rule.appliesTo(n)) {
1566                     return rule;
1567                 }
1568             }
1569             return null;
1570         }
1571
1572         public String select(FixedDecimal n) {
1573             Rule r = selectRule(n);
1574             // since we have explict 'other', we don't need this.
1575             //            if (r == null) {
1576             //                return KEYWORD_OTHER;
1577             //            }
1578             return r.getKeyword();
1579         }
1580
1581         public Set<String> getKeywords() {
1582             Set<String> result = new LinkedHashSet<String>();
1583             for (Rule rule : rules) {
1584                 result.add(rule.getKeyword());
1585             }
1586             // since we have explict 'other', we don't need this.
1587             //result.add(KEYWORD_OTHER);
1588             return result;
1589         }
1590
1591         public boolean isLimited(String keyword, SampleType sampleType) {
1592             if (hasExplicitBoundingInfo) {
1593                 FixedDecimalSamples mySamples = getDecimalSamples(keyword, sampleType);
1594                 return mySamples == null ? true : mySamples.bounded;
1595             }
1596
1597             return computeLimited(keyword, sampleType);
1598         }
1599
1600         public boolean computeLimited(String keyword, SampleType sampleType) {
1601             // if all rules with this keyword are limited, it's limited,
1602             // and if there's no rule with this keyword, it's unlimited
1603             boolean result = false;
1604             for (Rule rule : rules) {
1605                 if (keyword.equals(rule.getKeyword())) {
1606                     if (!rule.isLimited(sampleType)) {
1607                         return false;
1608                     }
1609                     result = true;
1610                 }
1611             }
1612             return result;
1613         }
1614
1615         public String toString() {
1616             StringBuilder builder = new StringBuilder();
1617             for (Rule rule : rules) {
1618                 if (builder.length() != 0) {
1619                     builder.append(CATEGORY_SEPARATOR);
1620                 }
1621                 builder.append(rule);
1622             }
1623             return builder.toString();
1624         }
1625
1626         public String getRules(String keyword) {
1627             for (Rule rule : rules) {
1628                 if (rule.getKeyword().equals(keyword)) {
1629                     return rule.getConstraint();
1630                 }
1631             }
1632             return null;
1633         }
1634
1635         public boolean select(FixedDecimal sample, String keyword) {
1636             for (Rule rule : rules) {
1637                 if (rule.getKeyword().equals(keyword) && rule.appliesTo(sample)) {
1638                     return true;
1639                 }
1640             }
1641             return false;
1642         }
1643
1644         public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
1645             for (Rule rule : rules) {
1646                 if (rule.getKeyword().equals(keyword)) {
1647                     return sampleType == SampleType.INTEGER ? rule.integerSamples : rule.decimalSamples;
1648                 }
1649             }
1650             return null;
1651         }
1652     }
1653
1654     /**
1655      * @deprecated This API is ICU internal only.
1656      * @internal
1657      */
1658     public enum StandardPluralCategories {
1659         zero,
1660         one,
1661         two,
1662         few,
1663         many,
1664         other;
1665         static StandardPluralCategories forString(String s) {
1666             StandardPluralCategories a;
1667             try {
1668                 a = valueOf(s);
1669             } catch (Exception e) {
1670                 return null;
1671             }
1672             return a;
1673         }
1674     }
1675
1676     @SuppressWarnings("unused")
1677     private boolean addConditional(Set<FixedDecimal> toAddTo, Set<FixedDecimal> others, double trial) {
1678         boolean added;
1679         FixedDecimal toAdd = new FixedDecimal(trial);
1680         if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) {
1681             others.add(toAdd);
1682             added = true;
1683         } else {
1684             added = false;
1685         }
1686         return added;
1687     }
1688
1689
1690
1691     // -------------------------------------------------------------------------
1692     // Static class methods.
1693     // -------------------------------------------------------------------------
1694
1695     /**
1696      * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given
1697      * locale.
1698      * Same as forLocale(locale, PluralType.CARDINAL).
1699      *
1700      * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1701      * For these predefined rules, see CLDR page at
1702      * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1703      *
1704      * @param locale The locale for which a <code>PluralRules</code> object is
1705      *   returned.
1706      * @return The predefined <code>PluralRules</code> object for this locale.
1707      *   If there's no predefined rules for this locale, the rules
1708      *   for the closest parent in the locale hierarchy that has one will
1709      *   be returned.  The final fallback always returns the default
1710      *   rules.
1711      * @stable ICU 3.8
1712      */
1713     public static PluralRules forLocale(ULocale locale) {
1714         return Factory.getDefaultFactory().forLocale(locale, PluralType.CARDINAL);
1715     }
1716
1717     /**
1718      * Provides access to the predefined <code>PluralRules</code> for a given
1719      * locale and the plural type.
1720      *
1721      * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1722      * For these predefined rules, see CLDR page at
1723      * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1724      *
1725      * @param locale The locale for which a <code>PluralRules</code> object is
1726      *   returned.
1727      * @param type The plural type (e.g., cardinal or ordinal).
1728      * @return The predefined <code>PluralRules</code> object for this locale.
1729      *   If there's no predefined rules for this locale, the rules
1730      *   for the closest parent in the locale hierarchy that has one will
1731      *   be returned.  The final fallback always returns the default
1732      *   rules.
1733      * @stable ICU 50
1734      */
1735     public static PluralRules forLocale(ULocale locale, PluralType type) {
1736         return Factory.getDefaultFactory().forLocale(locale, type);
1737     }
1738
1739     /*
1740      * Checks whether a token is a valid keyword.
1741      *
1742      * @param token the token to be checked
1743      * @return true if the token is a valid keyword.
1744      */
1745     private static boolean isValidKeyword(String token) {
1746         return ALLOWED_ID.containsAll(token);
1747     }
1748
1749     /*
1750      * Creates a new <code>PluralRules</code> object.  Immutable.
1751      */
1752     private PluralRules(RuleList rules) {
1753         this.rules = rules;
1754         this.keywords = Collections.unmodifiableSet(rules.getKeywords());
1755     }
1756
1757     /**
1758      * @internal
1759      * @deprecated This API is ICU internal only.
1760      */
1761     @Override
1762     public int hashCode() {
1763         return rules.hashCode();
1764     }
1765     /**
1766      * Given a number, returns the keyword of the first rule that applies to
1767      * the number.
1768      *
1769      * @param number The number for which the rule has to be determined.
1770      * @return The keyword of the selected rule.
1771      * @stable ICU 4.0
1772      */
1773     public String select(double number) {
1774         return rules.select(new FixedDecimal(number));
1775     }
1776
1777     /**
1778      * Given a number, returns the keyword of the first rule that applies to
1779      * the number.
1780      *
1781      * @param number The number for which the rule has to be determined.
1782      * @return The keyword of the selected rule.
1783      * @internal
1784      * @deprecated This API is ICU internal only.
1785      */
1786     public String select(double number, int countVisibleFractionDigits, long fractionaldigits) {
1787         return rules.select(new FixedDecimal(number, countVisibleFractionDigits, fractionaldigits));
1788     }
1789
1790     /**
1791      * Given a number information, returns the keyword of the first rule that applies to
1792      * the number.
1793      *
1794      * @param sample The number information for which the rule has to be determined.
1795      * @return The keyword of the selected rule.
1796      * @internal
1797      * @deprecated This API is ICU internal only.
1798      */
1799     public String select(FixedDecimal sample) {
1800         return rules.select(sample);
1801     }
1802
1803
1804     /**
1805      * Given a number information, and keyword, return whether the keyword would match the number.
1806      *
1807      * @param sample The number information for which the rule has to be determined.
1808      * @param keyword The keyword to filter on
1809      * @internal
1810      * @deprecated This API is ICU internal only.
1811      */
1812     public boolean matches(FixedDecimal sample, String keyword) {
1813         return rules.select(sample, keyword);
1814     }
1815
1816     /**
1817      * Returns a set of all rule keywords used in this <code>PluralRules</code>
1818      * object.  The rule "other" is always present by default.
1819      *
1820      * @return The set of keywords.
1821      * @stable ICU 3.8
1822      */
1823     public Set<String> getKeywords() {
1824         return keywords;
1825     }
1826
1827     /**
1828      * Returns the unique value that this keyword matches, or {@link #NO_UNIQUE_VALUE}
1829      * if the keyword matches multiple values or is not defined for this PluralRules.
1830      *
1831      * @param keyword the keyword to check for a unique value
1832      * @return The unique value for the keyword, or NO_UNIQUE_VALUE.
1833      * @stable ICU 4.8
1834      */
1835     public double getUniqueKeywordValue(String keyword) {
1836         Collection<Double> values = getAllKeywordValues(keyword);
1837         if (values != null && values.size() == 1) {
1838             return values.iterator().next();
1839         }
1840         return NO_UNIQUE_VALUE;
1841     }
1842
1843     /**
1844      * Returns all the values that trigger this keyword, or null if the number of such
1845      * values is unlimited.
1846      *
1847      * @param keyword the keyword
1848      * @return the values that trigger this keyword, or null.  The returned collection
1849      * is immutable. It will be empty if the keyword is not defined.
1850      * @stable ICU 4.8
1851      */
1852     public Collection<Double> getAllKeywordValues(String keyword) {
1853         return getAllKeywordValues(keyword, SampleType.INTEGER);
1854     }
1855
1856     /**
1857      * Returns all the values that trigger this keyword, or null if the number of such
1858      * values is unlimited.
1859      *
1860      * @param keyword the keyword
1861      * @return the values that trigger this keyword, or null.  The returned collection
1862      * is immutable. It will be empty if the keyword is not defined.
1863      * 
1864      * @internal
1865      * @deprecated This API is ICU internal only.
1866      */
1867     public Collection<Double> getAllKeywordValues(String keyword, SampleType type) {
1868         if (!isLimited(keyword, type)) {
1869             return null;
1870         }
1871         Collection<Double> samples = getSamples(keyword, type);
1872         return samples == null ? null : Collections.unmodifiableCollection(samples);
1873     }
1874
1875     /**
1876      * Returns a list of values for which select() would return that keyword,
1877      * or null if the keyword is not defined. The returned collection is unmodifiable.
1878      * The returned list is not complete, and there might be additional values that
1879      * would return the keyword.
1880      *
1881      * @param keyword the keyword to test
1882      * @return a list of values matching the keyword.
1883      * @stable ICU 4.8
1884      */
1885     public Collection<Double> getSamples(String keyword) {
1886         return getSamples(keyword, SampleType.INTEGER);
1887     }
1888
1889     /**
1890      * Returns a list of values for which select() would return that keyword,
1891      * or null if the keyword is not defined.
1892      * The returned collection is unmodifiable.
1893      * The returned list is not complete, and there might be additional values that
1894      * would return the keyword. The keyword might be defined, and yet have an empty set of samples,
1895      * IF there are samples for the other sampleType.
1896      *
1897      * @param keyword the keyword to test
1898      * @return a list of values matching the keyword.
1899      * @deprecated ICU internal only
1900      * @internal 
1901      */
1902     public Collection<Double> getSamples(String keyword, SampleType sampleType) {
1903         if (!keywords.contains(keyword)) {
1904             return null;
1905         }
1906         Set<Double> result = new TreeSet<Double>();
1907
1908         if (rules.hasExplicitBoundingInfo) {
1909             FixedDecimalSamples samples = rules.getDecimalSamples(keyword, sampleType);
1910             return samples == null ? Collections.unmodifiableSet(result)
1911                     : Collections.unmodifiableSet(samples.addSamples(result));
1912         }
1913
1914         // hack in case the rule is created without explicit samples
1915         int maxCount = isLimited(keyword, sampleType) ? Integer.MAX_VALUE : 20;
1916
1917         switch (sampleType) {
1918         case INTEGER:
1919             for (int i = 0; i < 200; ++i) {
1920                 if (!addSample(keyword, i, maxCount, result)) {
1921                     break;
1922                 }
1923             }
1924             addSample(keyword, 1000000, maxCount, result); // hack for Welsh
1925             break;
1926         case DECIMAL:
1927             for (int i = 0; i < 2000; ++i) {
1928                 if (!addSample(keyword, new FixedDecimal(i/10d, 1), maxCount, result)) {
1929                     break;
1930                 }
1931             }
1932             addSample(keyword, new FixedDecimal(1000000d, 1), maxCount, result); // hack for Welsh
1933             break;
1934         }
1935         return result.size() == 0 ? null : Collections.unmodifiableSet(result);
1936     }
1937
1938     /**
1939      * @internal
1940      * @deprecated This API is ICU internal only.
1941      */
1942     public boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
1943         String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue());
1944         if (selectedKeyword.equals(keyword)) {
1945             result.add(sample.doubleValue());
1946             if (--maxCount < 0) {
1947                 return false;
1948             }
1949         }
1950         return true;
1951     }
1952
1953     /**
1954      * Returns a list of values for which select() would return that keyword,
1955      * or null if the keyword is not defined or no samples are available.
1956      * The returned collection is unmodifiable.
1957      * The returned list is not complete, and there might be additional values that
1958      * would return the keyword.
1959      *
1960      * @param keyword the keyword to test
1961      * @return a list of values matching the keyword.
1962      * @internal
1963      * @deprecated This API is ICU internal only.
1964      */
1965     public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
1966         return rules.getDecimalSamples(keyword, sampleType);
1967     }
1968
1969     /**
1970      * Returns the set of locales for which PluralRules are known.
1971      * @return the set of locales for which PluralRules are known, as a list
1972      * @draft ICU 4.2
1973      * @provisional This API might change or be removed in a future release.
1974      */
1975     public static ULocale[] getAvailableULocales() {
1976         return Factory.getDefaultFactory().getAvailableULocales();
1977     }
1978
1979     /**
1980      * Returns the 'functionally equivalent' locale with respect to
1981      * plural rules.  Calling PluralRules.forLocale with the functionally equivalent
1982      * locale, and with the provided locale, returns rules that behave the same.
1983      * <br/>
1984      * All locales with the same functionally equivalent locale have
1985      * plural rules that behave the same.  This is not exaustive;
1986      * there may be other locales whose plural rules behave the same
1987      * that do not have the same equivalent locale.
1988      *
1989      * @param locale the locale to check
1990      * @param isAvailable if not null and of length > 0, this will hold 'true' at
1991      * index 0 if locale is directly defined (without fallback) as having plural rules
1992      * @return the functionally-equivalent locale
1993      * @draft ICU 4.2
1994      * @provisional This API might change or be removed in a future release.
1995      */
1996     public static ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
1997         return Factory.getDefaultFactory().getFunctionalEquivalent(locale, isAvailable);
1998     }
1999
2000     /**
2001      * {@inheritDoc}
2002      * @stable ICU 3.8
2003      */
2004     public String toString() {
2005         return rules.toString();
2006     }
2007
2008     /**
2009      * {@inheritDoc}
2010      * @stable ICU 3.8
2011      */
2012     public boolean equals(Object rhs) {
2013         return rhs instanceof PluralRules && equals((PluralRules)rhs);
2014     }
2015
2016     /**
2017      * Returns true if rhs is equal to this.
2018      * @param rhs the PluralRules to compare to.
2019      * @return true if this and rhs are equal.
2020      * @stable ICU 3.8
2021      */
2022     // TODO Optimize this
2023     public boolean equals(PluralRules rhs) {
2024         return rhs != null && toString().equals(rhs.toString());
2025     }
2026
2027     /**
2028      * Status of the keyword for the rules, given a set of explicit values.
2029      * 
2030      * @draft ICU 50
2031      * @provisional This API might change or be removed in a future release.
2032      */
2033     public enum KeywordStatus {
2034         /**
2035          * The keyword is not valid for the rules.
2036          * 
2037          * @draft ICU 50
2038          * @provisional This API might change or be removed in a future release.
2039          */
2040         INVALID,
2041         /**
2042          * The keyword is valid, but unused (it is covered by the explicit values, OR has no values for the given {@link SampleType}).
2043          * 
2044          * @draft ICU 50
2045          * @provisional This API might change or be removed in a future release.
2046          */
2047         SUPPRESSED,
2048         /**
2049          * The keyword is valid, used, and has a single possible value (before considering explicit values).
2050          * 
2051          * @draft ICU 50
2052          * @provisional This API might change or be removed in a future release.
2053          */
2054         UNIQUE,
2055         /**
2056          * The keyword is valid, used, not unique, and has a finite set of values.
2057          * 
2058          * @draft ICU 50
2059          * @provisional This API might change or be removed in a future release.
2060          */
2061         BOUNDED,
2062         /**
2063          * The keyword is valid but not bounded; there indefinitely many matching values.
2064          * 
2065          * @draft ICU 50
2066          * @provisional This API might change or be removed in a future release.
2067          */
2068         UNBOUNDED
2069     }
2070
2071     /**
2072      * Find the status for the keyword, given a certain set of explicit values.
2073      * 
2074      * @param keyword
2075      *            the particular keyword (call rules.getKeywords() to get the valid ones)
2076      * @param offset
2077      *            the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
2078      *            checking against the keyword values.
2079      * @param explicits
2080      *            a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
2081      * @param uniqueValue
2082      *            If non null, set to the unique value.
2083      * @return the KeywordStatus
2084      * @draft ICU 50
2085      * @provisional This API might change or be removed in a future release.
2086      */
2087     public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
2088             Output<Double> uniqueValue) {
2089         return getKeywordStatus(keyword, offset, explicits, uniqueValue, SampleType.INTEGER);
2090     }
2091     /**
2092      * Find the status for the keyword, given a certain set of explicit values.
2093      * 
2094      * @param keyword
2095      *            the particular keyword (call rules.getKeywords() to get the valid ones)
2096      * @param offset
2097      *            the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
2098      *            checking against the keyword values.
2099      * @param explicits
2100      *            a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
2101      * @param uniqueValue
2102      *            If non null, set to the unique value.
2103      * @return the KeywordStatus
2104      * @internal
2105      * @provisional This API might change or be removed in a future release.
2106      */
2107     public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
2108             Output<Double> uniqueValue, SampleType sampleType) {
2109         if (uniqueValue != null) {
2110             uniqueValue.value = null;
2111         }
2112
2113         if (!keywords.contains(keyword)) {
2114             return KeywordStatus.INVALID;
2115         }
2116
2117         if (!isLimited(keyword, sampleType)) {
2118             return KeywordStatus.UNBOUNDED;
2119         }
2120
2121         Collection<Double> values = getSamples(keyword, sampleType);
2122
2123         int originalSize = values.size();
2124
2125         if (explicits == null) {
2126             explicits = Collections.emptySet();
2127         }
2128
2129         // Quick check on whether there are multiple elements
2130
2131         if (originalSize > explicits.size()) {
2132             if (originalSize == 1) {
2133                 if (uniqueValue != null) {
2134                     uniqueValue.value = values.iterator().next();
2135                 }
2136                 return KeywordStatus.UNIQUE;
2137             }
2138             return KeywordStatus.BOUNDED;
2139         }
2140
2141         // Compute if the quick test is insufficient.
2142
2143         HashSet<Double> subtractedSet = new HashSet<Double>(values);
2144         for (Double explicit : explicits) {
2145             subtractedSet.remove(explicit - offset);
2146         }
2147         if (subtractedSet.size() == 0) {
2148             return KeywordStatus.SUPPRESSED;
2149         }
2150
2151         if (uniqueValue != null && subtractedSet.size() == 1) {
2152             uniqueValue.value = subtractedSet.iterator().next();
2153         }
2154
2155         return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED;
2156     }
2157
2158     /**
2159      * @internal
2160      * @deprecated This API is ICU internal only.
2161      */
2162     public String getRules(String keyword) {
2163         return rules.getRules(keyword);
2164     }
2165
2166     private void writeObject(
2167             ObjectOutputStream out)
2168                     throws IOException {
2169         throw new NotSerializableException();
2170     }
2171
2172     private void readObject(ObjectInputStream in
2173             ) throws IOException, ClassNotFoundException {
2174         throw new NotSerializableException();
2175     }
2176
2177     private Object writeReplace() throws ObjectStreamException {
2178         return new PluralRulesSerialProxy(toString());
2179     }
2180
2181     /**
2182      * @internal
2183      * @deprecated internal
2184      */
2185     public int compareTo(PluralRules other) {
2186         return toString().compareTo(other.toString());
2187     }
2188
2189     /**
2190      * @internal
2191      * @deprecated internal
2192      */
2193     public Boolean isLimited(String keyword) {
2194         return rules.isLimited(keyword, SampleType.INTEGER);
2195     }
2196
2197     /**
2198      * @internal
2199      * @deprecated internal
2200      */
2201     public boolean isLimited(String keyword, SampleType sampleType) {
2202         return rules.isLimited(keyword, sampleType);
2203     }
2204
2205     /**
2206      * @internal
2207      * @deprecated internal
2208      */
2209     public boolean computeLimited(String keyword, SampleType sampleType) {
2210         return rules.computeLimited(keyword, sampleType);
2211     }
2212 }