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