Home | History | Annotate | Download | only in text
      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 
     11 package android.icu.text;
     12 
     13 import java.io.IOException;
     14 import java.io.NotSerializableException;
     15 import java.io.ObjectInputStream;
     16 import java.io.ObjectOutputStream;
     17 import java.io.ObjectStreamException;
     18 import java.io.Serializable;
     19 import java.text.ParseException;
     20 import java.util.ArrayList;
     21 import java.util.Collection;
     22 import java.util.Collections;
     23 import java.util.HashSet;
     24 import java.util.Iterator;
     25 import java.util.LinkedHashSet;
     26 import java.util.List;
     27 import java.util.Locale;
     28 import java.util.Set;
     29 import java.util.TreeSet;
     30 import java.util.regex.Pattern;
     31 
     32 import android.icu.impl.PluralRulesLoader;
     33 import android.icu.util.Output;
     34 import 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 digitswithout trailing zerosas 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  */
    169 public 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