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