14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm/* GENERATED SOURCE. DO NOT MODIFY. */
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm// © 2016 and later: Unicode, Inc. and others.
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm// License & terms of use: http://www.unicode.org/copyright.html#License
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm/*
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm *******************************************************************************
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Copyright (C) 2007-2016, International Business Machines Corporation and
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * others. All Rights Reserved.
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm *******************************************************************************
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm */
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmpackage android.icu.text;
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.io.IOException;
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.io.NotSerializableException;
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.io.ObjectInputStream;
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.io.ObjectOutputStream;
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.io.ObjectStreamException;
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.io.Serializable;
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.text.ParseException;
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.ArrayList;
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.Collection;
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.Collections;
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.HashSet;
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.Iterator;
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.LinkedHashSet;
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.List;
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.Locale;
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.Set;
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.TreeSet;
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport java.util.regex.Pattern;
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport android.icu.impl.PluralRulesLoader;
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport android.icu.util.Output;
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport android.icu.util.ULocale;
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm/**
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Defines rules for mapping non-negative numeric values onto a small set of keywords.
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Rules are constructed from a text description, consisting of a series of keywords and conditions. The {@link #select}
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * method examines each condition in order and returns the keyword for the first condition that matches the number. If
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * none match, {@link #KEYWORD_OTHER} is returned.
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * A PluralRules object is immutable. It contains caches for sample values, but those are synchronized.
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * PluralRules is Serializable so that it can be used in formatters, which are serializable.
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * For more information, details, and tips for writing rules, see the <a
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * href="http://www.unicode.org/draft/reports/tr35/tr35.html#Language_Plural_Rules">LDML spec, C.11 Language Plural
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Rules</a>
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Examples:
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm *
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <pre>
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * &quot;one: n is 1; few: n in 2..4&quot;
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </pre>
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * This defines two rules, for 'one' and 'few'. The condition for 'one' is "n is 1" which means that the number must be
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * equal to 1 for this condition to pass. The condition for 'few' is "n in 2..4" which means that the number must be
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * between 2 and 4 inclusive - and be an integer - for this condition to pass. All other numbers are assigned the
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * keyword "other" by the default rule.
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm *
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <pre>
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * &quot;zero: n is 0; one: n is 1; zero: n mod 100 in 1..19&quot;
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </pre>
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * This illustrates that the same keyword can be defined multiple times. Each rule is examined in order, and the first
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * keyword whose condition passes is the one returned. Also notes that a modulus is applied to n in the last rule. Thus
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * its condition holds for 119, 219, 319...
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm *
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <pre>
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * &quot;one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14&quot;
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </pre>
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * This illustrates conjunction and negation. The condition for 'few' has two parts, both of which must be met:
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * "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
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * previous example. The second part applies a different modulus and also uses negation, thus it matches all numbers
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * _not_ in 12, 13, 14, 112, 113, 114, 212, 213, 214...
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Syntax:
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <pre>
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * rules         = rule (';' rule)*
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * rule          = keyword ':' condition
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * keyword       = &lt;identifier&gt;
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * condition     = and_condition ('or' and_condition)*
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * and_condition = relation ('and' relation)*
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * relation      = not? expr not? rel not? range_list
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * expr          = ('n' | 'i' | 'f' | 'v' | 't') (mod value)?
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * not           = 'not' | '!'
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * rel           = 'in' | 'is' | '=' | '≠' | 'within'
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * mod           = 'mod' | '%'
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * range_list    = (range | value) (',' range_list)*
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * value         = digit+
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * digit         = 0|1|2|3|4|5|6|7|8|9
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * range         = value'..'value
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </pre>
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>Each <b>not</b> term inverts the meaning; however, there should not be more than one of them.</p>
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * The i, f, t, and v values are defined as follows:
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <ul>
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <li>i to be the integer digits.</li>
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <li>f to be the visible decimal digits, as an integer.</li>
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <li>t to be the visible decimal digits—without trailing zeros—as an integer.</li>
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <li>v to be the number of visible fraction digits.</li>
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <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>
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </ul>
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * Examples are in the following table:
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <table border='1' style="border-collapse:collapse">
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tbody>
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tr>
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <th>n</th>
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <th>i</th>
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <th>f</th>
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <th>v</th>
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tr>
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tr>
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1.0</td>
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td align="right">0</td>
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tr>
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tr>
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1.00</td>
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td align="right">0</td>
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>2</td>
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tr>
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tr>
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1.3</td>
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td align="right">3</td>
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tr>
1464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tr>
1474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1.03</td>
1484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td align="right">3</td>
1504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>2</td>
1514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tr>
1524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <tr>
1534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1.23</td>
1544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>1</td>
1554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td align="right">23</td>
1564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <td>2</td>
1574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tr>
1584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </tbody>
1594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </table>
1604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
1614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * An "identifier" is a sequence of characters that do not have the Unicode Pattern_Syntax or Pattern_White_Space
1624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * properties.
1634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * <p>
1644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * The difference between 'in' and 'within' is that 'in' only includes integers in the specified range, while 'within'
1654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * includes all values. Using 'within' with a range_list consisting entirely of values is the same as using 'in' (it's
1664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * not an error).
1674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm * </p>
1684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm */
1694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmpublic class PluralRules implements Serializable {
1704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze();
1724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    // TODO Remove RulesList by moving its API and fields into PluralRules.
1744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    /**
1754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @deprecated This API is ICU internal only.
1764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @hide original deprecated declaration
1774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @hide draft / provisional / internal are hidden on Android
1784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     */
1794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    @Deprecated
1804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    public static final String CATEGORY_SEPARATOR = ";  ";
1814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    /**
1824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @deprecated This API is ICU internal only.
1834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @hide original deprecated declaration
1844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @hide draft / provisional / internal are hidden on Android
1854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     */
1864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    @Deprecated
1874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    public static final String KEYWORD_RULE_SEPARATOR = ": ";
1884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    private static final long serialVersionUID = 1;
1904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    private final RuleList rules;
1924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    private final transient Set<String> keywords;
1934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    /**
1954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * Provides a factory for returning plural rules
1964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     *
1974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @deprecated This API is ICU internal only.
1984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @hide original deprecated declaration
1994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     * @hide draft / provisional / internal are hidden on Android
2004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm     */
2014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    @Deprecated
2024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    public static abstract class Factory {
2034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        /**
2044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * Sole constructor
2054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @deprecated This API is ICU internal only.
2064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide original deprecated declaration
2074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide draft / provisional / internal are hidden on Android
2084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         */
2094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        @Deprecated
2104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        protected Factory() {
2114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        }
2124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        /**
2144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * Provides access to the predefined <code>PluralRules</code> for a given locale and the plural type.
2154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *
2164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * <p>
2174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. For these predefined
2184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * rules, see CLDR page at http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
2194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *
2204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @param locale
2214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *            The locale for which a <code>PluralRules</code> object is returned.
2224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @param type
2234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *            The plural type (e.g., cardinal or ordinal).
2244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @return The predefined <code>PluralRules</code> object for this locale. If there's no predefined rules for
2254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *         this locale, the rules for the closest parent in the locale hierarchy that has one will be returned.
2264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *         The final fallback always returns the default rules.
2274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @deprecated This API is ICU internal only.
2284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide original deprecated declaration
2294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide draft / provisional / internal are hidden on Android
2304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         */
2314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        @Deprecated
2324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        public abstract PluralRules forLocale(ULocale locale, PluralType type);
2334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        /**
2354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * Utility for getting CARDINAL rules.
2364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @param locale the locale
2374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @return plural rules.
2384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @deprecated This API is ICU internal only.
2394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide original deprecated declaration
2404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide draft / provisional / internal are hidden on Android
2414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         */
2424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        @Deprecated
2434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        public final PluralRules forLocale(ULocale locale) {
2444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return forLocale(locale, PluralType.CARDINAL);
2454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        }
2464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        /**
2484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * Returns the locales for which there is plurals data.
2494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *
2504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @deprecated This API is ICU internal only.
2514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide original deprecated declaration
2524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide draft / provisional / internal are hidden on Android
2534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         */
2544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        @Deprecated
2554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        public abstract ULocale[] getAvailableULocales();
2564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        /**
2584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * Returns the 'functionally equivalent' locale with respect to plural rules. Calling PluralRules.forLocale with
2594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * the functionally equivalent locale, and with the provided locale, returns rules that behave the same. <br>
2604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * All locales with the same functionally equivalent locale have plural rules that behave the same. This is not
2614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * exaustive; there may be other locales whose plural rules behave the same that do not have the same equivalent
2624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * locale.
2634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *
2644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @param locale
2654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *            the locale to check
2664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @param isAvailable
2674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *            if not null and of length &gt; 0, this will hold 'true' at index 0 if locale is directly defined
2684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         *            (without fallback) as having plural rules
2694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @return the functionally-equivalent locale
2704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @deprecated This API is ICU internal only.
2714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide original deprecated declaration
2724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @hide draft / provisional / internal are hidden on Android
2734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         */
2744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        @Deprecated
2754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        public abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable);
2764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        /**
2784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * Returns the default factory.
2794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm         * @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(FixedDecimal 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    private enum Operand {
412        n,
413        i,
414        f,
415        t,
416        v,
417        w,
418        /* deprecated */
419        j;
420    }
421
422    /**
423     * @deprecated This API is ICU internal only.
424     * @hide original deprecated declaration
425     * @hide draft / provisional / internal are hidden on Android
426     */
427    @Deprecated
428    public static class FixedDecimal extends Number implements Comparable<FixedDecimal> {
429        private static final long serialVersionUID = -4756200506571685661L;
430        /**
431         * @deprecated This API is ICU internal only.
432         * @hide original deprecated declaration
433         * @hide draft / provisional / internal are hidden on Android
434         */
435        @Deprecated
436        public final double source;
437        /**
438         * @deprecated This API is ICU internal only.
439         * @hide original deprecated declaration
440         * @hide draft / provisional / internal are hidden on Android
441         */
442        @Deprecated
443        public final int visibleDecimalDigitCount;
444        /**
445         * @deprecated This API is ICU internal only.
446         * @hide original deprecated declaration
447         * @hide draft / provisional / internal are hidden on Android
448         */
449        @Deprecated
450        public final int visibleDecimalDigitCountWithoutTrailingZeros;
451        /**
452         * @deprecated This API is ICU internal only.
453         * @hide original deprecated declaration
454         * @hide draft / provisional / internal are hidden on Android
455         */
456        @Deprecated
457        public final long decimalDigits;
458        /**
459         * @deprecated This API is ICU internal only.
460         * @hide original deprecated declaration
461         * @hide draft / provisional / internal are hidden on Android
462         */
463        @Deprecated
464        public final long decimalDigitsWithoutTrailingZeros;
465        /**
466         * @deprecated This API is ICU internal only.
467         * @hide original deprecated declaration
468         * @hide draft / provisional / internal are hidden on Android
469         */
470        @Deprecated
471        public final long integerValue;
472        /**
473         * @deprecated This API is ICU internal only.
474         * @hide original deprecated declaration
475         * @hide draft / provisional / internal are hidden on Android
476         */
477        @Deprecated
478        public final boolean hasIntegerValue;
479        /**
480         * @deprecated This API is ICU internal only.
481         * @hide original deprecated declaration
482         * @hide draft / provisional / internal are hidden on Android
483         */
484        @Deprecated
485        public final boolean isNegative;
486        private final int baseFactor;
487
488        /**
489         * @deprecated This API is ICU internal only.
490         * @hide original deprecated declaration
491         * @hide draft / provisional / internal are hidden on Android
492         */
493        @Deprecated
494        public double getSource() {
495            return source;
496        }
497
498        /**
499         * @deprecated This API is ICU internal only.
500         * @hide original deprecated declaration
501         * @hide draft / provisional / internal are hidden on Android
502         */
503        @Deprecated
504        public int getVisibleDecimalDigitCount() {
505            return visibleDecimalDigitCount;
506        }
507
508        /**
509         * @deprecated This API is ICU internal only.
510         * @hide original deprecated declaration
511         * @hide draft / provisional / internal are hidden on Android
512         */
513        @Deprecated
514        public int getVisibleDecimalDigitCountWithoutTrailingZeros() {
515            return visibleDecimalDigitCountWithoutTrailingZeros;
516        }
517
518        /**
519         * @deprecated This API is ICU internal only.
520         * @hide original deprecated declaration
521         * @hide draft / provisional / internal are hidden on Android
522         */
523        @Deprecated
524        public long getDecimalDigits() {
525            return decimalDigits;
526        }
527
528        /**
529         * @deprecated This API is ICU internal only.
530         * @hide original deprecated declaration
531         * @hide draft / provisional / internal are hidden on Android
532         */
533        @Deprecated
534        public long getDecimalDigitsWithoutTrailingZeros() {
535            return decimalDigitsWithoutTrailingZeros;
536        }
537
538        /**
539         * @deprecated This API is ICU internal only.
540         * @hide original deprecated declaration
541         * @hide draft / provisional / internal are hidden on Android
542         */
543        @Deprecated
544        public long getIntegerValue() {
545            return integerValue;
546        }
547
548        /**
549         * @deprecated This API is ICU internal only.
550         * @hide original deprecated declaration
551         * @hide draft / provisional / internal are hidden on Android
552         */
553        @Deprecated
554        public boolean isHasIntegerValue() {
555            return hasIntegerValue;
556        }
557
558        /**
559         * @deprecated This API is ICU internal only.
560         * @hide original deprecated declaration
561         * @hide draft / provisional / internal are hidden on Android
562         */
563        @Deprecated
564        public boolean isNegative() {
565            return isNegative;
566        }
567
568        /**
569         * @deprecated This API is ICU internal only.
570         * @hide original deprecated declaration
571         * @hide draft / provisional / internal are hidden on Android
572         */
573        @Deprecated
574        public int getBaseFactor() {
575            return baseFactor;
576        }
577
578        static final long MAX = (long)1E18;
579
580        /**
581         * @deprecated This API is ICU internal only.
582         * @param n is the original number
583         * @param v number of digits to the right of the decimal place. e.g 1.00 = 2 25. = 0
584         * @param f Corresponds to f in the plural rules grammar.
585         *   The digits to the right of the decimal place as an integer. e.g 1.10 = 10
586         * @hide original deprecated declaration
587         * @hide draft / provisional / internal are hidden on Android
588         */
589        @Deprecated
590        public FixedDecimal(double n, int v, long f) {
591            isNegative = n < 0;
592            source = isNegative ? -n : n;
593            visibleDecimalDigitCount = v;
594            decimalDigits = f;
595            integerValue = n > MAX
596                    ? MAX
597                            : (long)n;
598            hasIntegerValue = source == integerValue;
599            // check values. TODO make into unit test.
600            //
601            //            long visiblePower = (int) Math.pow(10, v);
602            //            if (fractionalDigits > visiblePower) {
603            //                throw new IllegalArgumentException();
604            //            }
605            //            double fraction = intValue + (fractionalDigits / (double) visiblePower);
606            //            if (fraction != source) {
607            //                double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
608            //                if (diff > 0.00000001d) {
609            //                    throw new IllegalArgumentException();
610            //                }
611            //            }
612            if (f == 0) {
613                decimalDigitsWithoutTrailingZeros = 0;
614                visibleDecimalDigitCountWithoutTrailingZeros = 0;
615            } else {
616                long fdwtz = f;
617                int trimmedCount = v;
618                while ((fdwtz%10) == 0) {
619                    fdwtz /= 10;
620                    --trimmedCount;
621                }
622                decimalDigitsWithoutTrailingZeros = fdwtz;
623                visibleDecimalDigitCountWithoutTrailingZeros = trimmedCount;
624            }
625            baseFactor = (int) Math.pow(10, v);
626        }
627
628        /**
629         * @deprecated This API is ICU internal only.
630         * @hide original deprecated declaration
631         * @hide draft / provisional / internal are hidden on Android
632         */
633        @Deprecated
634        public FixedDecimal(double n, int v) {
635            this(n,v,getFractionalDigits(n, v));
636        }
637
638        private static int getFractionalDigits(double n, int v) {
639            if (v == 0) {
640                return 0;
641            } else {
642                if (n < 0) {
643                    n = -n;
644                }
645                int baseFactor = (int) Math.pow(10, v);
646                long scaled = Math.round(n * baseFactor);
647                return (int) (scaled % baseFactor);
648            }
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 FixedDecimal(double n) {
658            this(n, decimals(n));
659        }
660
661        /**
662         * @deprecated This API is ICU internal only.
663         * @hide original deprecated declaration
664         * @hide draft / provisional / internal are hidden on Android
665         */
666        @Deprecated
667        public FixedDecimal(long n) {
668            this(n,0);
669        }
670
671        private static final long MAX_INTEGER_PART = 1000000000;
672        /**
673         * Return a guess as to the number of decimals that would be displayed. This is only a guess; callers should
674         * always supply the decimals explicitly if possible. Currently, it is up to 6 decimals (without trailing zeros).
675         * Returns 0 for infinities and nans.
676         * @deprecated This API is ICU internal only.
677         * @hide original deprecated declaration
678         * @hide draft / provisional / internal are hidden on Android
679         *
680         */
681        @Deprecated
682        public static int decimals(double n) {
683            // Ugly...
684            if (Double.isInfinite(n) || Double.isNaN(n)) {
685                return 0;
686            }
687            if (n < 0) {
688                n = -n;
689            }
690            if (n == Math.floor(n)) {
691                return 0;
692            }
693            if (n < MAX_INTEGER_PART) {
694                long temp = (long)(n * 1000000) % 1000000; // get 6 decimals
695                for (int mask = 10, digits = 6; digits > 0; mask *= 10, --digits) {
696                    if ((temp % mask) != 0) {
697                        return digits;
698                    }
699                }
700                return 0;
701            } else {
702                String buf = String.format(Locale.ENGLISH, "%1.15e", n);
703                int ePos = buf.lastIndexOf('e');
704                int expNumPos = ePos + 1;
705                if (buf.charAt(expNumPos) == '+') {
706                    expNumPos++;
707                }
708                String exponentStr = buf.substring(expNumPos);
709                int exponent = Integer.parseInt(exponentStr);
710                int numFractionDigits = ePos - 2 - exponent;
711                if (numFractionDigits < 0) {
712                    return 0;
713                }
714                for (int i=ePos-1; numFractionDigits > 0; --i) {
715                    if (buf.charAt(i) != '0') {
716                        break;
717                    }
718                    --numFractionDigits;
719                }
720                return numFractionDigits;
721            }
722        }
723
724        /**
725         * @deprecated This API is ICU internal only.
726         * @hide original deprecated declaration
727         * @hide draft / provisional / internal are hidden on Android
728         */
729        @Deprecated
730        public FixedDecimal (String n) {
731            // Ugly, but for samples we don't care.
732            this(Double.parseDouble(n), getVisibleFractionCount(n));
733        }
734
735        private static int getVisibleFractionCount(String value) {
736            value = value.trim();
737            int decimalPos = value.indexOf('.') + 1;
738            if (decimalPos == 0) {
739                return 0;
740            } else {
741                return value.length() - decimalPos;
742            }
743        }
744
745        /**
746         * @deprecated This API is ICU internal only.
747         * @hide original deprecated declaration
748         * @hide draft / provisional / internal are hidden on Android
749         */
750        @Deprecated
751        public double get(Operand operand) {
752            switch(operand) {
753            default: return source;
754            case i: return integerValue;
755            case f: return decimalDigits;
756            case t: return decimalDigitsWithoutTrailingZeros;
757            case v: return visibleDecimalDigitCount;
758            case w: return visibleDecimalDigitCountWithoutTrailingZeros;
759            }
760        }
761
762        /**
763         * @deprecated This API is ICU internal only.
764         * @hide original deprecated declaration
765         * @hide draft / provisional / internal are hidden on Android
766         */
767        @Deprecated
768        public static Operand getOperand(String t) {
769            return Operand.valueOf(t);
770        }
771
772        /**
773         * We're not going to care about NaN.
774         * @deprecated This API is ICU internal only.
775         * @hide original deprecated declaration
776         * @hide draft / provisional / internal are hidden on Android
777         */
778        @Override
779        @Deprecated
780        public int compareTo(FixedDecimal other) {
781            if (integerValue != other.integerValue) {
782                return integerValue < other.integerValue ? -1 : 1;
783            }
784            if (source != other.source) {
785                return source < other.source ? -1 : 1;
786            }
787            if (visibleDecimalDigitCount != other.visibleDecimalDigitCount) {
788                return visibleDecimalDigitCount < other.visibleDecimalDigitCount ? -1 : 1;
789            }
790            long diff = decimalDigits - other.decimalDigits;
791            if (diff != 0) {
792                return diff < 0 ? -1 : 1;
793            }
794            return 0;
795        }
796
797        /**
798         * @deprecated This API is ICU internal only.
799         * @hide original deprecated declaration
800         * @hide draft / provisional / internal are hidden on Android
801         */
802        @Deprecated
803        @Override
804        public boolean equals(Object arg0) {
805            if (arg0 == null) {
806                return false;
807            }
808            if (arg0 == this) {
809                return true;
810            }
811            if (!(arg0 instanceof FixedDecimal)) {
812                return false;
813            }
814            FixedDecimal other = (FixedDecimal)arg0;
815            return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount && decimalDigits == other.decimalDigits;
816        }
817
818        /**
819         * @deprecated This API is ICU internal only.
820         * @hide original deprecated declaration
821         * @hide draft / provisional / internal are hidden on Android
822         */
823        @Deprecated
824        @Override
825        public int hashCode() {
826            // TODO Auto-generated method stub
827            return (int)(decimalDigits + 37 * (visibleDecimalDigitCount + (int)(37 * source)));
828        }
829
830        /**
831         * @deprecated This API is ICU internal only.
832         * @hide original deprecated declaration
833         * @hide draft / provisional / internal are hidden on Android
834         */
835        @Deprecated
836        @Override
837        public String toString() {
838            return String.format("%." + visibleDecimalDigitCount + "f", source);
839        }
840
841        /**
842         * @deprecated This API is ICU internal only.
843         * @hide original deprecated declaration
844         * @hide draft / provisional / internal are hidden on Android
845         */
846        @Deprecated
847        public boolean hasIntegerValue() {
848            return hasIntegerValue;
849        }
850
851        /**
852         * @deprecated This API is ICU internal only.
853         * @hide original deprecated declaration
854         * @hide draft / provisional / internal are hidden on Android
855         */
856        @Deprecated
857        @Override
858        public int intValue() {
859            // TODO Auto-generated method stub
860            return (int)integerValue;
861        }
862
863        /**
864         * @deprecated This API is ICU internal only.
865         * @hide original deprecated declaration
866         * @hide draft / provisional / internal are hidden on Android
867         */
868        @Deprecated
869        @Override
870        public long longValue() {
871            return integerValue;
872        }
873
874        /**
875         * @deprecated This API is ICU internal only.
876         * @hide original deprecated declaration
877         * @hide draft / provisional / internal are hidden on Android
878         */
879        @Deprecated
880        @Override
881        public float floatValue() {
882            return (float) source;
883        }
884
885        /**
886         * @deprecated This API is ICU internal only.
887         * @hide original deprecated declaration
888         * @hide draft / provisional / internal are hidden on Android
889         */
890        @Deprecated
891        @Override
892        public double doubleValue() {
893            return isNegative ? -source : source;
894        }
895
896        /**
897         * @deprecated This API is ICU internal only.
898         * @hide original deprecated declaration
899         * @hide draft / provisional / internal are hidden on Android
900         */
901        @Deprecated
902        public long getShiftedValue() {
903            return integerValue * baseFactor + decimalDigits;
904        }
905
906        private void writeObject(
907                ObjectOutputStream out)
908                        throws IOException {
909            throw new NotSerializableException();
910        }
911
912        private void readObject(ObjectInputStream in
913                ) throws IOException, ClassNotFoundException {
914            throw new NotSerializableException();
915        }
916    }
917
918    /**
919     * Selection parameter for either integer-only or decimal-only.
920     * @deprecated This API is ICU internal only.
921     * @hide original deprecated declaration
922     * @hide draft / provisional / internal are hidden on Android
923     */
924    @Deprecated
925    public enum SampleType {
926        /**
927         * @deprecated This API is ICU internal only.
928         * @hide draft / provisional / internal are hidden on Android
929         */
930        @Deprecated
931        INTEGER,
932        /**
933         * @deprecated This API is ICU internal only.
934         * @hide draft / provisional / internal are hidden on Android
935         */
936        @Deprecated
937        DECIMAL
938    }
939
940    /**
941     * A range of NumberInfo that includes all values with the same visibleFractionDigitCount.
942     * @deprecated This API is ICU internal only.
943     * @hide original deprecated declaration
944     * @hide draft / provisional / internal are hidden on Android
945     */
946    @Deprecated
947    public static class FixedDecimalRange {
948        /**
949         * @deprecated This API is ICU internal only.
950         * @hide original deprecated declaration
951         * @hide draft / provisional / internal are hidden on Android
952         */
953        @Deprecated
954        public final FixedDecimal start;
955        /**
956         * @deprecated This API is ICU internal only.
957         * @hide original deprecated declaration
958         * @hide draft / provisional / internal are hidden on Android
959         */
960        @Deprecated
961        public final FixedDecimal end;
962        /**
963         * @deprecated This API is ICU internal only.
964         * @hide original deprecated declaration
965         * @hide draft / provisional / internal are hidden on Android
966         */
967        @Deprecated
968        public FixedDecimalRange(FixedDecimal start, FixedDecimal end) {
969            if (start.visibleDecimalDigitCount != end.visibleDecimalDigitCount) {
970                throw new IllegalArgumentException("Ranges must have the same number of visible decimals: " + start + "~" + end);
971            }
972            this.start = start;
973            this.end = end;
974        }
975        /**
976         * @deprecated This API is ICU internal only.
977         * @hide original deprecated declaration
978         * @hide draft / provisional / internal are hidden on Android
979         */
980        @Deprecated
981        @Override
982        public String toString() {
983            return start + (end == start ? "" : "~" + end);
984        }
985    }
986
987    /**
988     * A list of NumberInfo that includes all values with the same visibleFractionDigitCount.
989     * @deprecated This API is ICU internal only.
990     * @hide original deprecated declaration
991     * @hide draft / provisional / internal are hidden on Android
992     */
993    @Deprecated
994    public static class FixedDecimalSamples {
995        /**
996         * @deprecated This API is ICU internal only.
997         * @hide original deprecated declaration
998         * @hide draft / provisional / internal are hidden on Android
999         */
1000        @Deprecated
1001        public final SampleType sampleType;
1002        /**
1003         * @deprecated This API is ICU internal only.
1004         * @hide original deprecated declaration
1005         * @hide draft / provisional / internal are hidden on Android
1006         */
1007        @Deprecated
1008        public final Set<FixedDecimalRange> samples;
1009        /**
1010         * @deprecated This API is ICU internal only.
1011         * @hide original deprecated declaration
1012         * @hide draft / provisional / internal are hidden on Android
1013         */
1014        @Deprecated
1015        public final boolean bounded;
1016        /**
1017         * The samples must be immutable.
1018         * @param sampleType
1019         * @param samples
1020         */
1021        private FixedDecimalSamples(SampleType sampleType, Set<FixedDecimalRange> samples, boolean bounded) {
1022            super();
1023            this.sampleType = sampleType;
1024            this.samples = samples;
1025            this.bounded = bounded;
1026        }
1027        /*
1028         * Parse a list of the form described in CLDR. The source must be trimmed.
1029         */
1030        static FixedDecimalSamples parse(String source) {
1031            SampleType sampleType2;
1032            boolean bounded2 = true;
1033            boolean haveBound = false;
1034            Set<FixedDecimalRange> samples2 = new LinkedHashSet<FixedDecimalRange>();
1035
1036            if (source.startsWith("integer")) {
1037                sampleType2 = SampleType.INTEGER;
1038            } else if (source.startsWith("decimal")) {
1039                sampleType2 = SampleType.DECIMAL;
1040            } else {
1041                throw new IllegalArgumentException("Samples must start with 'integer' or 'decimal'");
1042            }
1043            source = source.substring(7).trim(); // remove both
1044
1045            for (String range : COMMA_SEPARATED.split(source)) {
1046                if (range.equals("…") || range.equals("...")) {
1047                    bounded2 = false;
1048                    haveBound = true;
1049                    continue;
1050                }
1051                if (haveBound) {
1052                    throw new IllegalArgumentException("Can only have … at the end of samples: " + range);
1053                }
1054                String[] rangeParts = TILDE_SEPARATED.split(range);
1055                switch (rangeParts.length) {
1056                case 1:
1057                    FixedDecimal sample = new FixedDecimal(rangeParts[0]);
1058                    checkDecimal(sampleType2, sample);
1059                    samples2.add(new FixedDecimalRange(sample, sample));
1060                    break;
1061                case 2:
1062                    FixedDecimal start = new FixedDecimal(rangeParts[0]);
1063                    FixedDecimal end = new FixedDecimal(rangeParts[1]);
1064                    checkDecimal(sampleType2, start);
1065                    checkDecimal(sampleType2, end);
1066                    samples2.add(new FixedDecimalRange(start, end));
1067                    break;
1068                default: throw new IllegalArgumentException("Ill-formed number range: " + range);
1069                }
1070            }
1071            return new FixedDecimalSamples(sampleType2, Collections.unmodifiableSet(samples2), bounded2);
1072        }
1073
1074        private static void checkDecimal(SampleType sampleType2, FixedDecimal sample) {
1075            if ((sampleType2 == SampleType.INTEGER) != (sample.getVisibleDecimalDigitCount() == 0)) {
1076                throw new IllegalArgumentException("Ill-formed number range: " + sample);
1077            }
1078        }
1079
1080        /**
1081         * @deprecated This API is ICU internal only.
1082         * @hide original deprecated declaration
1083         * @hide draft / provisional / internal are hidden on Android
1084         */
1085        @Deprecated
1086        public Set<Double> addSamples(Set<Double> result) {
1087            for (FixedDecimalRange item : samples) {
1088                // we have to convert to longs so we don't get strange double issues
1089                long startDouble = item.start.getShiftedValue();
1090                long endDouble = item.end.getShiftedValue();
1091
1092                for (long d = startDouble; d <= endDouble; d += 1) {
1093                    result.add(d/(double)item.start.baseFactor);
1094                }
1095            }
1096            return result;
1097        }
1098
1099        /**
1100         * @deprecated This API is ICU internal only.
1101         * @hide original deprecated declaration
1102         * @hide draft / provisional / internal are hidden on Android
1103         */
1104        @Deprecated
1105        @Override
1106        public String toString() {
1107            StringBuilder b = new StringBuilder("@").append(sampleType.toString().toLowerCase(Locale.ENGLISH));
1108            boolean first = true;
1109            for (FixedDecimalRange item : samples) {
1110                if (first) {
1111                    first = false;
1112                } else {
1113                    b.append(",");
1114                }
1115                b.append(' ').append(item);
1116            }
1117            if (!bounded) {
1118                b.append(", …");
1119            }
1120            return b.toString();
1121        }
1122
1123        /**
1124         * @deprecated This API is ICU internal only.
1125         * @hide original deprecated declaration
1126         * @hide draft / provisional / internal are hidden on Android
1127         */
1128        @Deprecated
1129        public Set<FixedDecimalRange> getSamples() {
1130            return samples;
1131        }
1132
1133        /**
1134         * @deprecated This API is ICU internal only.
1135         * @hide original deprecated declaration
1136         * @hide draft / provisional / internal are hidden on Android
1137         */
1138        @Deprecated
1139        public void getStartEndSamples(Set<FixedDecimal> target) {
1140            for (FixedDecimalRange item : samples) {
1141                target.add(item.start);
1142                target.add(item.end);
1143            }
1144        }
1145    }
1146
1147    /*
1148     * A constraint on a number.
1149     */
1150    private interface Constraint extends Serializable {
1151        /*
1152         * Returns true if the number fulfills the constraint.
1153         * @param n the number to test, >= 0.
1154         */
1155        boolean isFulfilled(FixedDecimal n);
1156
1157        /*
1158         * Returns false if an unlimited number of values fulfills the
1159         * constraint.
1160         */
1161        boolean isLimited(SampleType sampleType);
1162    }
1163
1164    static class SimpleTokenizer {
1165        static final UnicodeSet BREAK_AND_IGNORE = new UnicodeSet(0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x20).freeze();
1166        static final UnicodeSet BREAK_AND_KEEP = new UnicodeSet('!', '!', '%', '%', ',', ',', '.', '.', '=', '=').freeze();
1167        static String[] split(String source) {
1168            int last = -1;
1169            List<String> result = new ArrayList<String>();
1170            for (int i = 0; i < source.length(); ++i) {
1171                char ch = source.charAt(i);
1172                if (BREAK_AND_IGNORE.contains(ch)) {
1173                    if (last >= 0) {
1174                        result.add(source.substring(last,i));
1175                        last = -1;
1176                    }
1177                } else if (BREAK_AND_KEEP.contains(ch)) {
1178                    if (last >= 0) {
1179                        result.add(source.substring(last,i));
1180                    }
1181                    result.add(source.substring(i,i+1));
1182                    last = -1;
1183                } else if (last < 0) {
1184                    last = i;
1185                }
1186            }
1187            if (last >= 0) {
1188                result.add(source.substring(last));
1189            }
1190            return result.toArray(new String[result.size()]);
1191        }
1192    }
1193
1194    /*
1195     * syntax:
1196     * condition :       or_condition
1197     *                   and_condition
1198     * or_condition :    and_condition 'or' condition
1199     * and_condition :   relation
1200     *                   relation 'and' relation
1201     * relation :        in_relation
1202     *                   within_relation
1203     * in_relation :     not? expr not? in not? range
1204     * within_relation : not? expr not? 'within' not? range
1205     * not :             'not'
1206     *                   '!'
1207     * expr :            'n'
1208     *                   'n' mod value
1209     * mod :             'mod'
1210     *                   '%'
1211     * in :              'in'
1212     *                   'is'
1213     *                   '='
1214     *                   '≠'
1215     * value :           digit+
1216     * digit :           0|1|2|3|4|5|6|7|8|9
1217     * range :           value'..'value
1218     */
1219    private static Constraint parseConstraint(String description)
1220            throws ParseException {
1221
1222        Constraint result = null;
1223        String[] or_together = OR_SEPARATED.split(description);
1224        for (int i = 0; i < or_together.length; ++i) {
1225            Constraint andConstraint = null;
1226            String[] and_together = AND_SEPARATED.split(or_together[i]);
1227            for (int j = 0; j < and_together.length; ++j) {
1228                Constraint newConstraint = NO_CONSTRAINT;
1229
1230                String condition = and_together[j].trim();
1231                String[] tokens = SimpleTokenizer.split(condition);
1232
1233                int mod = 0;
1234                boolean inRange = true;
1235                boolean integersOnly = true;
1236                double lowBound = Long.MAX_VALUE;
1237                double highBound = Long.MIN_VALUE;
1238                long[] vals = null;
1239
1240                int x = 0;
1241                String t = tokens[x++];
1242                boolean hackForCompatibility = false;
1243                Operand operand;
1244                try {
1245                    operand = FixedDecimal.getOperand(t);
1246                } catch (Exception e) {
1247                    throw unexpected(t, condition);
1248                }
1249                if (x < tokens.length) {
1250                    t = tokens[x++];
1251                    if ("mod".equals(t) || "%".equals(t)) {
1252                        mod = Integer.parseInt(tokens[x++]);
1253                        t = nextToken(tokens, x++, condition);
1254                    }
1255                    if ("not".equals(t)) {
1256                        inRange = !inRange;
1257                        t = nextToken(tokens, x++, condition);
1258                        if ("=".equals(t)) {
1259                            throw unexpected(t, condition);
1260                        }
1261                    } else if ("!".equals(t)) {
1262                        inRange = !inRange;
1263                        t = nextToken(tokens, x++, condition);
1264                        if (!"=".equals(t)) {
1265                            throw unexpected(t, condition);
1266                        }
1267                    }
1268                    if ("is".equals(t) || "in".equals(t) || "=".equals(t)) {
1269                        hackForCompatibility = "is".equals(t);
1270                        if (hackForCompatibility && !inRange) {
1271                            throw unexpected(t, condition);
1272                        }
1273                        t = nextToken(tokens, x++, condition);
1274                    } else if ("within".equals(t)) {
1275                        integersOnly = false;
1276                        t = nextToken(tokens, x++, condition);
1277                    } else {
1278                        throw unexpected(t, condition);
1279                    }
1280                    if ("not".equals(t)) {
1281                        if (!hackForCompatibility && !inRange) {
1282                            throw unexpected(t, condition);
1283                        }
1284                        inRange = !inRange;
1285                        t = nextToken(tokens, x++, condition);
1286                    }
1287
1288                    List<Long> valueList = new ArrayList<Long>();
1289
1290                    // the token t is always one item ahead
1291                    while (true) {
1292                        long low = Long.parseLong(t);
1293                        long high = low;
1294                        if (x < tokens.length) {
1295                            t = nextToken(tokens, x++, condition);
1296                            if (t.equals(".")) {
1297                                t = nextToken(tokens, x++, condition);
1298                                if (!t.equals(".")) {
1299                                    throw unexpected(t, condition);
1300                                }
1301                                t = nextToken(tokens, x++, condition);
1302                                high = Long.parseLong(t);
1303                                if (x < tokens.length) {
1304                                    t = nextToken(tokens, x++, condition);
1305                                    if (!t.equals(",")) { // adjacent number: 1 2
1306                                        // no separator, fail
1307                                        throw unexpected(t, condition);
1308                                    }
1309                                }
1310                            } else if (!t.equals(",")) { // adjacent number: 1 2
1311                                // no separator, fail
1312                                throw unexpected(t, condition);
1313                            }
1314                        }
1315                        // at this point, either we are out of tokens, or t is ','
1316                        if (low > high) {
1317                            throw unexpected(low + "~" + high, condition);
1318                        } else if (mod != 0 && high >= mod) {
1319                            throw unexpected(high + ">mod=" + mod, condition);
1320                        }
1321                        valueList.add(low);
1322                        valueList.add(high);
1323                        lowBound = Math.min(lowBound, low);
1324                        highBound = Math.max(highBound, high);
1325                        if (x >= tokens.length) {
1326                            break;
1327                        }
1328                        t = nextToken(tokens, x++, condition);
1329                    }
1330
1331                    if (t.equals(",")) {
1332                        throw unexpected(t, condition);
1333                    }
1334
1335                    if (valueList.size() == 2) {
1336                        vals = null;
1337                    } else {
1338                        vals = new long[valueList.size()];
1339                        for (int k = 0; k < vals.length; ++k) {
1340                            vals[k] = valueList.get(k);
1341                        }
1342                    }
1343
1344                    // Hack to exclude "is not 1,2"
1345                    if (lowBound != highBound && hackForCompatibility && !inRange) {
1346                        throw unexpected("is not <range>", condition);
1347                    }
1348
1349                    newConstraint =
1350                            new RangeConstraint(mod, inRange, operand, integersOnly, lowBound, highBound, vals);
1351                }
1352
1353                if (andConstraint == null) {
1354                    andConstraint = newConstraint;
1355                } else {
1356                    andConstraint = new AndConstraint(andConstraint,
1357                            newConstraint);
1358                }
1359            }
1360
1361            if (result == null) {
1362                result = andConstraint;
1363            } else {
1364                result = new OrConstraint(result, andConstraint);
1365            }
1366        }
1367        return result;
1368    }
1369
1370    static final Pattern AT_SEPARATED = Pattern.compile("\\s*\\Q\\E@\\s*");
1371    static final Pattern OR_SEPARATED = Pattern.compile("\\s*or\\s*");
1372    static final Pattern AND_SEPARATED = Pattern.compile("\\s*and\\s*");
1373    static final Pattern COMMA_SEPARATED = Pattern.compile("\\s*,\\s*");
1374    static final Pattern DOTDOT_SEPARATED = Pattern.compile("\\s*\\Q..\\E\\s*");
1375    static final Pattern TILDE_SEPARATED = Pattern.compile("\\s*~\\s*");
1376    static final Pattern SEMI_SEPARATED = Pattern.compile("\\s*;\\s*");
1377
1378
1379    /* Returns a parse exception wrapping the token and context strings. */
1380    private static ParseException unexpected(String token, String context) {
1381        return new ParseException("unexpected token '" + token +
1382                "' in '" + context + "'", -1);
1383    }
1384
1385    /*
1386     * Returns the token at x if available, else throws a parse exception.
1387     */
1388    private static String nextToken(String[] tokens, int x, String context)
1389            throws ParseException {
1390        if (x < tokens.length) {
1391            return tokens[x];
1392        }
1393        throw new ParseException("missing token at end of '" + context + "'", -1);
1394    }
1395
1396    /*
1397     * Syntax:
1398     * rule : keyword ':' condition
1399     * keyword: <identifier>
1400     */
1401    private static Rule parseRule(String description) throws ParseException {
1402        if (description.length() == 0) {
1403            return DEFAULT_RULE;
1404        }
1405
1406        description = description.toLowerCase(Locale.ENGLISH);
1407
1408        int x = description.indexOf(':');
1409        if (x == -1) {
1410            throw new ParseException("missing ':' in rule description '" +
1411                    description + "'", 0);
1412        }
1413
1414        String keyword = description.substring(0, x).trim();
1415        if (!isValidKeyword(keyword)) {
1416            throw new ParseException("keyword '" + keyword +
1417                    " is not valid", 0);
1418        }
1419
1420        description = description.substring(x+1).trim();
1421        String[] constraintOrSamples = AT_SEPARATED.split(description);
1422        boolean sampleFailure = false;
1423        FixedDecimalSamples integerSamples = null, decimalSamples = null;
1424        switch (constraintOrSamples.length) {
1425        case 1: break;
1426        case 2:
1427            integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
1428            if (integerSamples.sampleType == SampleType.DECIMAL) {
1429                decimalSamples = integerSamples;
1430                integerSamples = null;
1431            }
1432            break;
1433        case 3:
1434            integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
1435            decimalSamples = FixedDecimalSamples.parse(constraintOrSamples[2]);
1436            if (integerSamples.sampleType != SampleType.INTEGER || decimalSamples.sampleType != SampleType.DECIMAL) {
1437                throw new IllegalArgumentException("Must have @integer then @decimal in " + description);
1438            }
1439            break;
1440        default:
1441            throw new IllegalArgumentException("Too many samples in " + description);
1442        }
1443        if (sampleFailure) {
1444            throw new IllegalArgumentException("Ill-formed samples—'@' characters.");
1445        }
1446
1447        // 'other' is special, and must have no rules; all other keywords must have rules.
1448        boolean isOther = keyword.equals("other");
1449        if (isOther != (constraintOrSamples[0].length() == 0)) {
1450            throw new IllegalArgumentException("The keyword 'other' must have no constraints, just samples.");
1451        }
1452
1453        Constraint constraint;
1454        if (isOther) {
1455            constraint = NO_CONSTRAINT;
1456        } else {
1457            constraint = parseConstraint(constraintOrSamples[0]);
1458        }
1459        return new Rule(keyword, constraint, integerSamples, decimalSamples);
1460    }
1461
1462
1463    /*
1464     * Syntax:
1465     * rules : rule
1466     *         rule ';' rules
1467     */
1468    private static RuleList parseRuleChain(String description)
1469            throws ParseException {
1470        RuleList result = new RuleList();
1471        // remove trailing ;
1472        if (description.endsWith(";")) {
1473            description = description.substring(0,description.length()-1);
1474        }
1475        String[] rules = SEMI_SEPARATED.split(description);
1476        for (int i = 0; i < rules.length; ++i) {
1477            Rule rule = parseRule(rules[i].trim());
1478            result.hasExplicitBoundingInfo |= rule.integerSamples != null || rule.decimalSamples != null;
1479            result.addRule(rule);
1480        }
1481        return result.finish();
1482    }
1483
1484    /*
1485     * An implementation of Constraint representing a modulus,
1486     * a range of values, and include/exclude. Provides lots of
1487     * convenience factory methods.
1488     */
1489    private static class RangeConstraint implements Constraint, Serializable {
1490        private static final long serialVersionUID = 1;
1491
1492        private final int mod;
1493        private final boolean inRange;
1494        private final boolean integersOnly;
1495        private final double lowerBound;
1496        private final double upperBound;
1497        private final long[] range_list;
1498        private final Operand operand;
1499
1500        RangeConstraint(int mod, boolean inRange, Operand operand, boolean integersOnly,
1501                double lowBound, double highBound, long[] vals) {
1502            this.mod = mod;
1503            this.inRange = inRange;
1504            this.integersOnly = integersOnly;
1505            this.lowerBound = lowBound;
1506            this.upperBound = highBound;
1507            this.range_list = vals;
1508            this.operand = operand;
1509        }
1510
1511        @Override
1512        public boolean isFulfilled(FixedDecimal number) {
1513            double n = number.get(operand);
1514            if ((integersOnly && (n - (long)n) != 0.0
1515                    || operand == Operand.j && number.visibleDecimalDigitCount != 0)) {
1516                return !inRange;
1517            }
1518            if (mod != 0) {
1519                n = n % mod;    // java % handles double numerator the way we want
1520            }
1521            boolean test = n >= lowerBound && n <= upperBound;
1522            if (test && range_list != null) {
1523                test = false;
1524                for (int i = 0; !test && i < range_list.length; i += 2) {
1525                    test = n >= range_list[i] && n <= range_list[i+1];
1526                }
1527            }
1528            return inRange == test;
1529        }
1530
1531        @Override
1532        public boolean isLimited(SampleType sampleType) {
1533            boolean valueIsZero = lowerBound == upperBound && lowerBound == 0d;
1534            boolean hasDecimals =
1535                    (operand == Operand.v || operand == Operand.w || operand == Operand.f || operand == Operand.t)
1536                    && inRange != valueIsZero; // either NOT f = zero or f = non-zero
1537            switch (sampleType) {
1538            case INTEGER:
1539                return hasDecimals // will be empty
1540                        || (operand == Operand.n || operand == Operand.i || operand == Operand.j)
1541                        && mod == 0
1542                        && inRange;
1543
1544            case DECIMAL:
1545                return  (!hasDecimals || operand == Operand.n || operand == Operand.j)
1546                        && (integersOnly || lowerBound == upperBound)
1547                        && mod == 0
1548                        && inRange;
1549            }
1550            return false;
1551        }
1552
1553        @Override
1554        public String toString() {
1555            StringBuilder result = new StringBuilder();
1556            result.append(operand);
1557            if (mod != 0) {
1558                result.append(" % ").append(mod);
1559            }
1560            boolean isList = lowerBound != upperBound;
1561            result.append(
1562                    !isList ? (inRange ? " = " : " != ")
1563                            : integersOnly ? (inRange ? " = " : " != ")
1564                                    : (inRange ? " within " : " not within ")
1565                    );
1566            if (range_list != null) {
1567                for (int i = 0; i < range_list.length; i += 2) {
1568                    addRange(result, range_list[i], range_list[i+1], i != 0);
1569                }
1570            } else {
1571                addRange(result, lowerBound, upperBound, false);
1572            }
1573            return result.toString();
1574        }
1575    }
1576
1577    private static void addRange(StringBuilder result, double lb, double ub, boolean addSeparator) {
1578        if (addSeparator) {
1579            result.append(",");
1580        }
1581        if (lb == ub) {
1582            result.append(format(lb));
1583        } else {
1584            result.append(format(lb) + ".." + format(ub));
1585        }
1586    }
1587
1588    private static String format(double lb) {
1589        long lbi = (long) lb;
1590        return lb == lbi ? String.valueOf(lbi) : String.valueOf(lb);
1591    }
1592
1593    /* Convenience base class for and/or constraints. */
1594    private static abstract class BinaryConstraint implements Constraint,
1595    Serializable {
1596        private static final long serialVersionUID = 1;
1597        protected final Constraint a;
1598        protected final Constraint b;
1599
1600        protected BinaryConstraint(Constraint a, Constraint b) {
1601            this.a = a;
1602            this.b = b;
1603        }
1604    }
1605
1606    /* A constraint representing the logical and of two constraints. */
1607    private static class AndConstraint extends BinaryConstraint {
1608        private static final long serialVersionUID = 7766999779862263523L;
1609
1610        AndConstraint(Constraint a, Constraint b) {
1611            super(a, b);
1612        }
1613
1614        @Override
1615        public boolean isFulfilled(FixedDecimal n) {
1616            return a.isFulfilled(n)
1617                    && b.isFulfilled(n);
1618        }
1619
1620        @Override
1621        public boolean isLimited(SampleType sampleType) {
1622            // we ignore the case where both a and b are unlimited but no values
1623            // satisfy both-- we still consider this 'unlimited'
1624            return a.isLimited(sampleType)
1625                    || b.isLimited(sampleType);
1626        }
1627
1628        @Override
1629        public String toString() {
1630            return a.toString() + " and " + b.toString();
1631        }
1632    }
1633
1634    /* A constraint representing the logical or of two constraints. */
1635    private static class OrConstraint extends BinaryConstraint {
1636        private static final long serialVersionUID = 1405488568664762222L;
1637
1638        OrConstraint(Constraint a, Constraint b) {
1639            super(a, b);
1640        }
1641
1642        @Override
1643        public boolean isFulfilled(FixedDecimal n) {
1644            return a.isFulfilled(n)
1645                    || b.isFulfilled(n);
1646        }
1647
1648        @Override
1649        public boolean isLimited(SampleType sampleType) {
1650            return a.isLimited(sampleType)
1651                    && b.isLimited(sampleType);
1652        }
1653
1654        @Override
1655        public String toString() {
1656            return a.toString() + " or " + b.toString();
1657        }
1658    }
1659
1660    /*
1661     * Implementation of Rule that uses a constraint.
1662     * Provides 'and' and 'or' to combine constraints.  Immutable.
1663     */
1664    private static class Rule implements Serializable {
1665        // TODO - Findbugs: Class android.icu.text.PluralRules$Rule defines non-transient
1666        // non-serializable instance field integerSamples. See ticket#10494.
1667        private static final long serialVersionUID = 1;
1668        private final String keyword;
1669        private final Constraint constraint;
1670        private final FixedDecimalSamples integerSamples;
1671        private final FixedDecimalSamples decimalSamples;
1672
1673        public Rule(String keyword, Constraint constraint, FixedDecimalSamples integerSamples, FixedDecimalSamples decimalSamples) {
1674            this.keyword = keyword;
1675            this.constraint = constraint;
1676            this.integerSamples = integerSamples;
1677            this.decimalSamples = decimalSamples;
1678        }
1679
1680        @SuppressWarnings("unused")
1681        public Rule and(Constraint c) {
1682            return new Rule(keyword, new AndConstraint(constraint, c), integerSamples, decimalSamples);
1683        }
1684
1685        @SuppressWarnings("unused")
1686        public Rule or(Constraint c) {
1687            return new Rule(keyword, new OrConstraint(constraint, c), integerSamples, decimalSamples);
1688        }
1689
1690        public String getKeyword() {
1691            return keyword;
1692        }
1693
1694        public boolean appliesTo(FixedDecimal n) {
1695            return constraint.isFulfilled(n);
1696        }
1697
1698        public boolean isLimited(SampleType sampleType) {
1699            return constraint.isLimited(sampleType);
1700        }
1701
1702        @Override
1703        public String toString() {
1704            return keyword + ": " + constraint.toString()
1705                    + (integerSamples == null ? "" : " " + integerSamples.toString())
1706                    + (decimalSamples == null ? "" : " " + decimalSamples.toString());
1707        }
1708
1709        /**
1710         * @deprecated This API is ICU internal only.
1711         * @hide draft / provisional / internal are hidden on Android
1712         */
1713        @Deprecated
1714        @Override
1715        public int hashCode() {
1716            return keyword.hashCode() ^ constraint.hashCode();
1717        }
1718
1719        public String getConstraint() {
1720            return constraint.toString();
1721        }
1722    }
1723
1724    private static class RuleList implements Serializable {
1725        private boolean hasExplicitBoundingInfo = false;
1726        private static final long serialVersionUID = 1;
1727        private final List<Rule> rules = new ArrayList<Rule>();
1728
1729        public RuleList addRule(Rule nextRule) {
1730            String keyword = nextRule.getKeyword();
1731            for (Rule rule : rules) {
1732                if (keyword.equals(rule.getKeyword())) {
1733                    throw new IllegalArgumentException("Duplicate keyword: " + keyword);
1734                }
1735            }
1736            rules.add(nextRule);
1737            return this;
1738        }
1739
1740        public RuleList finish() throws ParseException {
1741            // make sure that 'other' is present, and at the end.
1742            Rule otherRule = null;
1743            for (Iterator<Rule> it = rules.iterator(); it.hasNext();) {
1744                Rule rule = it.next();
1745                if ("other".equals(rule.getKeyword())) {
1746                    otherRule = rule;
1747                    it.remove();
1748                }
1749            }
1750            if (otherRule == null) {
1751                otherRule = parseRule("other:"); // make sure we have always have an 'other' a rule
1752            }
1753            rules.add(otherRule);
1754            return this;
1755        }
1756
1757        private Rule selectRule(FixedDecimal n) {
1758            for (Rule rule : rules) {
1759                if (rule.appliesTo(n)) {
1760                    return rule;
1761                }
1762            }
1763            return null;
1764        }
1765
1766        public String select(FixedDecimal n) {
1767            if (Double.isInfinite(n.source) || Double.isNaN(n.source)) {
1768                return KEYWORD_OTHER;
1769            }
1770            Rule r = selectRule(n);
1771            return r.getKeyword();
1772        }
1773
1774        public Set<String> getKeywords() {
1775            Set<String> result = new LinkedHashSet<String>();
1776            for (Rule rule : rules) {
1777                result.add(rule.getKeyword());
1778            }
1779            // since we have explict 'other', we don't need this.
1780            //result.add(KEYWORD_OTHER);
1781            return result;
1782        }
1783
1784        public boolean isLimited(String keyword, SampleType sampleType) {
1785            if (hasExplicitBoundingInfo) {
1786                FixedDecimalSamples mySamples = getDecimalSamples(keyword, sampleType);
1787                return mySamples == null ? true : mySamples.bounded;
1788            }
1789
1790            return computeLimited(keyword, sampleType);
1791        }
1792
1793        public boolean computeLimited(String keyword, SampleType sampleType) {
1794            // if all rules with this keyword are limited, it's limited,
1795            // and if there's no rule with this keyword, it's unlimited
1796            boolean result = false;
1797            for (Rule rule : rules) {
1798                if (keyword.equals(rule.getKeyword())) {
1799                    if (!rule.isLimited(sampleType)) {
1800                        return false;
1801                    }
1802                    result = true;
1803                }
1804            }
1805            return result;
1806        }
1807
1808        @Override
1809        public String toString() {
1810            StringBuilder builder = new StringBuilder();
1811            for (Rule rule : rules) {
1812                if (builder.length() != 0) {
1813                    builder.append(CATEGORY_SEPARATOR);
1814                }
1815                builder.append(rule);
1816            }
1817            return builder.toString();
1818        }
1819
1820        public String getRules(String keyword) {
1821            for (Rule rule : rules) {
1822                if (rule.getKeyword().equals(keyword)) {
1823                    return rule.getConstraint();
1824                }
1825            }
1826            return null;
1827        }
1828
1829        public boolean select(FixedDecimal sample, String keyword) {
1830            for (Rule rule : rules) {
1831                if (rule.getKeyword().equals(keyword) && rule.appliesTo(sample)) {
1832                    return true;
1833                }
1834            }
1835            return false;
1836        }
1837
1838        public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
1839            for (Rule rule : rules) {
1840                if (rule.getKeyword().equals(keyword)) {
1841                    return sampleType == SampleType.INTEGER ? rule.integerSamples : rule.decimalSamples;
1842                }
1843            }
1844            return null;
1845        }
1846    }
1847
1848    @SuppressWarnings("unused")
1849    private boolean addConditional(Set<FixedDecimal> toAddTo, Set<FixedDecimal> others, double trial) {
1850        boolean added;
1851        FixedDecimal toAdd = new FixedDecimal(trial);
1852        if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) {
1853            others.add(toAdd);
1854            added = true;
1855        } else {
1856            added = false;
1857        }
1858        return added;
1859    }
1860
1861
1862
1863    // -------------------------------------------------------------------------
1864    // Static class methods.
1865    // -------------------------------------------------------------------------
1866
1867    /**
1868     * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given
1869     * locale.
1870     * Same as forLocale(locale, PluralType.CARDINAL).
1871     *
1872     * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1873     * For these predefined rules, see CLDR page at
1874     * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1875     *
1876     * @param locale The locale for which a <code>PluralRules</code> object is
1877     *   returned.
1878     * @return The predefined <code>PluralRules</code> object for this locale.
1879     *   If there's no predefined rules for this locale, the rules
1880     *   for the closest parent in the locale hierarchy that has one will
1881     *   be returned.  The final fallback always returns the default
1882     *   rules.
1883     */
1884    public static PluralRules forLocale(ULocale locale) {
1885        return Factory.getDefaultFactory().forLocale(locale, PluralType.CARDINAL);
1886    }
1887
1888    /**
1889     * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given
1890     * {@link java.util.Locale}.
1891     * Same as forLocale(locale, PluralType.CARDINAL).
1892     *
1893     * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1894     * For these predefined rules, see CLDR page at
1895     * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1896     *
1897     * @param locale The locale for which a <code>PluralRules</code> object is
1898     *   returned.
1899     * @return The predefined <code>PluralRules</code> object for this locale.
1900     *   If there's no predefined rules for this locale, the rules
1901     *   for the closest parent in the locale hierarchy that has one will
1902     *   be returned.  The final fallback always returns the default
1903     *   rules.
1904     */
1905    public static PluralRules forLocale(Locale locale) {
1906        return forLocale(ULocale.forLocale(locale));
1907    }
1908
1909    /**
1910     * Provides access to the predefined <code>PluralRules</code> for a given
1911     * locale and the plural type.
1912     *
1913     * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1914     * For these predefined rules, see CLDR page at
1915     * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1916     *
1917     * @param locale The locale for which a <code>PluralRules</code> object is
1918     *   returned.
1919     * @param type The plural type (e.g., cardinal or ordinal).
1920     * @return The predefined <code>PluralRules</code> object for this locale.
1921     *   If there's no predefined rules for this locale, the rules
1922     *   for the closest parent in the locale hierarchy that has one will
1923     *   be returned.  The final fallback always returns the default
1924     *   rules.
1925     */
1926    public static PluralRules forLocale(ULocale locale, PluralType type) {
1927        return Factory.getDefaultFactory().forLocale(locale, type);
1928    }
1929
1930    /**
1931     * Provides access to the predefined <code>PluralRules</code> for a given
1932     * {@link java.util.Locale} and the plural type.
1933     *
1934     * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1935     * For these predefined rules, see CLDR page at
1936     * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1937     *
1938     * @param locale The locale for which a <code>PluralRules</code> object is
1939     *   returned.
1940     * @param type The plural type (e.g., cardinal or ordinal).
1941     * @return The predefined <code>PluralRules</code> object for this locale.
1942     *   If there's no predefined rules for this locale, the rules
1943     *   for the closest parent in the locale hierarchy that has one will
1944     *   be returned.  The final fallback always returns the default
1945     *   rules.
1946     */
1947    public static PluralRules forLocale(Locale locale, PluralType type) {
1948        return forLocale(ULocale.forLocale(locale), type);
1949    }
1950
1951    /*
1952     * Checks whether a token is a valid keyword.
1953     *
1954     * @param token the token to be checked
1955     * @return true if the token is a valid keyword.
1956     */
1957    private static boolean isValidKeyword(String token) {
1958        return ALLOWED_ID.containsAll(token);
1959    }
1960
1961    /*
1962     * Creates a new <code>PluralRules</code> object.  Immutable.
1963     */
1964    private PluralRules(RuleList rules) {
1965        this.rules = rules;
1966        this.keywords = Collections.unmodifiableSet(rules.getKeywords());
1967    }
1968
1969    /**
1970     * @deprecated This API is ICU internal only.
1971     * @hide original deprecated declaration
1972     * @hide draft / provisional / internal are hidden on Android
1973     */
1974    @Deprecated
1975    @Override
1976    public int hashCode() {
1977        return rules.hashCode();
1978    }
1979    /**
1980     * Given a number, returns the keyword of the first rule that applies to
1981     * the number.
1982     *
1983     * @param number The number for which the rule has to be determined.
1984     * @return The keyword of the selected rule.
1985     */
1986    public String select(double number) {
1987        return rules.select(new FixedDecimal(number));
1988    }
1989
1990    /**
1991     * Given a number, returns the keyword of the first rule that applies to
1992     * the number.
1993     *
1994     * @param number The number for which the rule has to be determined.
1995     * @return The keyword of the selected rule.
1996     * @deprecated This API is ICU internal only.
1997     * @hide original deprecated declaration
1998     * @hide draft / provisional / internal are hidden on Android
1999     */
2000    @Deprecated
2001    public String select(double number, int countVisibleFractionDigits, long fractionaldigits) {
2002        return rules.select(new FixedDecimal(number, countVisibleFractionDigits, fractionaldigits));
2003    }
2004
2005    /**
2006     * Given a number information, returns the keyword of the first rule that applies to
2007     * the number.
2008     *
2009     * @param number The number information for which the rule has to be determined.
2010     * @return The keyword of the selected rule.
2011     * @deprecated This API is ICU internal only.
2012     * @hide original deprecated declaration
2013     * @hide draft / provisional / internal are hidden on Android
2014     */
2015    @Deprecated
2016    public String select(FixedDecimal number) {
2017        return rules.select(number);
2018    }
2019
2020    /**
2021     * Given a number information, and keyword, return whether the keyword would match the number.
2022     *
2023     * @param sample The number information for which the rule has to be determined.
2024     * @param keyword The keyword to filter on
2025     * @deprecated This API is ICU internal only.
2026     * @hide original deprecated declaration
2027     * @hide draft / provisional / internal are hidden on Android
2028     */
2029    @Deprecated
2030    public boolean matches(FixedDecimal sample, String keyword) {
2031        return rules.select(sample, keyword);
2032    }
2033
2034    /**
2035     * Returns a set of all rule keywords used in this <code>PluralRules</code>
2036     * object.  The rule "other" is always present by default.
2037     *
2038     * @return The set of keywords.
2039     */
2040    public Set<String> getKeywords() {
2041        return keywords;
2042    }
2043
2044    /**
2045     * Returns the unique value that this keyword matches, or {@link #NO_UNIQUE_VALUE}
2046     * if the keyword matches multiple values or is not defined for this PluralRules.
2047     *
2048     * @param keyword the keyword to check for a unique value
2049     * @return The unique value for the keyword, or NO_UNIQUE_VALUE.
2050     */
2051    public double getUniqueKeywordValue(String keyword) {
2052        Collection<Double> values = getAllKeywordValues(keyword);
2053        if (values != null && values.size() == 1) {
2054            return values.iterator().next();
2055        }
2056        return NO_UNIQUE_VALUE;
2057    }
2058
2059    /**
2060     * Returns all the values that trigger this keyword, or null if the number of such
2061     * values is unlimited.
2062     *
2063     * @param keyword the keyword
2064     * @return the values that trigger this keyword, or null.  The returned collection
2065     * is immutable. It will be empty if the keyword is not defined.
2066     */
2067    public Collection<Double> getAllKeywordValues(String keyword) {
2068        return getAllKeywordValues(keyword, SampleType.INTEGER);
2069    }
2070
2071    /**
2072     * Returns all the values that trigger this keyword, or null if the number of such
2073     * values is unlimited.
2074     *
2075     * @param keyword the keyword
2076     * @param type the type of samples requested, INTEGER or DECIMAL
2077     * @return the values that trigger this keyword, or null.  The returned collection
2078     * is immutable. It will be empty if the keyword is not defined.
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    public Collection<Double> getAllKeywordValues(String keyword, SampleType type) {
2086        if (!isLimited(keyword, type)) {
2087            return null;
2088        }
2089        Collection<Double> samples = getSamples(keyword, type);
2090        return samples == null ? null : Collections.unmodifiableCollection(samples);
2091    }
2092
2093    /**
2094     * Returns a list of integer values for which select() would return that keyword,
2095     * or null if the keyword is not defined. The returned collection is unmodifiable.
2096     * The returned list is not complete, and there might be additional values that
2097     * would return the keyword.
2098     *
2099     * @param keyword the keyword to test
2100     * @return a list of values matching the keyword.
2101     */
2102    public Collection<Double> getSamples(String keyword) {
2103        return getSamples(keyword, SampleType.INTEGER);
2104    }
2105
2106    /**
2107     * Returns a list of values for which select() would return that keyword,
2108     * or null if the keyword is not defined.
2109     * The returned collection is unmodifiable.
2110     * The returned list is not complete, and there might be additional values that
2111     * would return the keyword. The keyword might be defined, and yet have an empty set of samples,
2112     * IF there are samples for the other sampleType.
2113     *
2114     * @param keyword the keyword to test
2115     * @param sampleType the type of samples requested, INTEGER or DECIMAL
2116     * @return a list of values matching the keyword.
2117     * @deprecated ICU internal only
2118     * @hide original deprecated declaration
2119     * @hide draft / provisional / internal are hidden on Android
2120     */
2121    @Deprecated
2122    public Collection<Double> getSamples(String keyword, SampleType sampleType) {
2123        if (!keywords.contains(keyword)) {
2124            return null;
2125        }
2126        Set<Double> result = new TreeSet<Double>();
2127
2128        if (rules.hasExplicitBoundingInfo) {
2129            FixedDecimalSamples samples = rules.getDecimalSamples(keyword, sampleType);
2130            return samples == null ? Collections.unmodifiableSet(result)
2131                    : Collections.unmodifiableSet(samples.addSamples(result));
2132        }
2133
2134        // hack in case the rule is created without explicit samples
2135        int maxCount = isLimited(keyword, sampleType) ? Integer.MAX_VALUE : 20;
2136
2137        switch (sampleType) {
2138        case INTEGER:
2139            for (int i = 0; i < 200; ++i) {
2140                if (!addSample(keyword, i, maxCount, result)) {
2141                    break;
2142                }
2143            }
2144            addSample(keyword, 1000000, maxCount, result); // hack for Welsh
2145            break;
2146        case DECIMAL:
2147            for (int i = 0; i < 2000; ++i) {
2148                if (!addSample(keyword, new FixedDecimal(i/10d, 1), maxCount, result)) {
2149                    break;
2150                }
2151            }
2152            addSample(keyword, new FixedDecimal(1000000d, 1), maxCount, result); // hack for Welsh
2153            break;
2154        }
2155        return result.size() == 0 ? null : Collections.unmodifiableSet(result);
2156    }
2157
2158    /**
2159     * @deprecated This API is ICU internal only.
2160     * @hide original deprecated declaration
2161     * @hide draft / provisional / internal are hidden on Android
2162     */
2163    @Deprecated
2164    public boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
2165        String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue());
2166        if (selectedKeyword.equals(keyword)) {
2167            result.add(sample.doubleValue());
2168            if (--maxCount < 0) {
2169                return false;
2170            }
2171        }
2172        return true;
2173    }
2174
2175    /**
2176     * Returns a list of values for which select() would return that keyword,
2177     * or null if the keyword is not defined or no samples are available.
2178     * The returned collection is unmodifiable.
2179     * The returned list is not complete, and there might be additional values that
2180     * would return the keyword.
2181     *
2182     * @param keyword the keyword to test
2183     * @param sampleType the type of samples requested, INTEGER or DECIMAL
2184     * @return a list of values matching the keyword.
2185     * @deprecated This API is ICU internal only.
2186     * @hide original deprecated declaration
2187     * @hide draft / provisional / internal are hidden on Android
2188     */
2189    @Deprecated
2190    public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
2191        return rules.getDecimalSamples(keyword, sampleType);
2192    }
2193
2194    /**
2195     * Returns the set of locales for which PluralRules are known.
2196     * @return the set of locales for which PluralRules are known, as a list
2197     * @hide draft / provisional / internal are hidden on Android
2198     */
2199    public static ULocale[] getAvailableULocales() {
2200        return Factory.getDefaultFactory().getAvailableULocales();
2201    }
2202
2203    /**
2204     * Returns the 'functionally equivalent' locale with respect to
2205     * plural rules.  Calling PluralRules.forLocale with the functionally equivalent
2206     * locale, and with the provided locale, returns rules that behave the same.
2207     * <br>
2208     * All locales with the same functionally equivalent locale have
2209     * plural rules that behave the same.  This is not exaustive;
2210     * there may be other locales whose plural rules behave the same
2211     * that do not have the same equivalent locale.
2212     *
2213     * @param locale the locale to check
2214     * @param isAvailable if not null and of length &gt; 0, this will hold 'true' at
2215     * index 0 if locale is directly defined (without fallback) as having plural rules
2216     * @return the functionally-equivalent locale
2217     * @hide draft / provisional / internal are hidden on Android
2218     */
2219    public static ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
2220        return Factory.getDefaultFactory().getFunctionalEquivalent(locale, isAvailable);
2221    }
2222
2223    /**
2224     * {@inheritDoc}
2225     */
2226    @Override
2227    public String toString() {
2228        return rules.toString();
2229    }
2230
2231    /**
2232     * {@inheritDoc}
2233     */
2234    @Override
2235    public boolean equals(Object rhs) {
2236        return rhs instanceof PluralRules && equals((PluralRules)rhs);
2237    }
2238
2239    /**
2240     * Returns true if rhs is equal to this.
2241     * @param rhs the PluralRules to compare to.
2242     * @return true if this and rhs are equal.
2243     */
2244    // TODO Optimize this
2245    public boolean equals(PluralRules rhs) {
2246        return rhs != null && toString().equals(rhs.toString());
2247    }
2248
2249    /**
2250     * Status of the keyword for the rules, given a set of explicit values.
2251     *
2252     * @hide draft / provisional / internal are hidden on Android
2253     */
2254    public enum KeywordStatus {
2255        /**
2256         * The keyword is not valid for the rules.
2257         *
2258         * @hide draft / provisional / internal are hidden on Android
2259         */
2260        INVALID,
2261        /**
2262         * The keyword is valid, but unused (it is covered by the explicit values, OR has no values for the given {@link SampleType}).
2263         *
2264         * @hide draft / provisional / internal are hidden on Android
2265         */
2266        SUPPRESSED,
2267        /**
2268         * The keyword is valid, used, and has a single possible value (before considering explicit values).
2269         *
2270         * @hide draft / provisional / internal are hidden on Android
2271         */
2272        UNIQUE,
2273        /**
2274         * The keyword is valid, used, not unique, and has a finite set of values.
2275         *
2276         * @hide draft / provisional / internal are hidden on Android
2277         */
2278        BOUNDED,
2279        /**
2280         * The keyword is valid but not bounded; there indefinitely many matching values.
2281         *
2282         * @hide draft / provisional / internal are hidden on Android
2283         */
2284        UNBOUNDED
2285    }
2286
2287    /**
2288     * Find the status for the keyword, given a certain set of explicit values.
2289     *
2290     * @param keyword
2291     *            the particular keyword (call rules.getKeywords() to get the valid ones)
2292     * @param offset
2293     *            the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
2294     *            checking against the keyword values.
2295     * @param explicits
2296     *            a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
2297     * @param uniqueValue
2298     *            If non null, set to the unique value.
2299     * @return the KeywordStatus
2300     * @hide draft / provisional / internal are hidden on Android
2301     */
2302    public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
2303            Output<Double> uniqueValue) {
2304        return getKeywordStatus(keyword, offset, explicits, uniqueValue, SampleType.INTEGER);
2305    }
2306    /**
2307     * Find the status for the keyword, given a certain set of explicit values.
2308     *
2309     * @param keyword
2310     *            the particular keyword (call rules.getKeywords() to get the valid ones)
2311     * @param offset
2312     *            the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
2313     *            checking against the keyword values.
2314     * @param explicits
2315     *            a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
2316     * @param sampleType
2317     *            request KeywordStatus relative to INTEGER or DECIMAL values
2318     * @param uniqueValue
2319     *            If non null, set to the unique value.
2320     * @return the KeywordStatus
2321     * @deprecated This API is ICU internal only.
2322     * @hide original deprecated declaration
2323     * @hide draft / provisional / internal are hidden on Android
2324     */
2325    @Deprecated
2326    public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
2327            Output<Double> uniqueValue, SampleType sampleType) {
2328        if (uniqueValue != null) {
2329            uniqueValue.value = null;
2330        }
2331
2332        if (!keywords.contains(keyword)) {
2333            return KeywordStatus.INVALID;
2334        }
2335
2336        if (!isLimited(keyword, sampleType)) {
2337            return KeywordStatus.UNBOUNDED;
2338        }
2339
2340        Collection<Double> values = getSamples(keyword, sampleType);
2341
2342        int originalSize = values.size();
2343
2344        if (explicits == null) {
2345            explicits = Collections.emptySet();
2346        }
2347
2348        // Quick check on whether there are multiple elements
2349
2350        if (originalSize > explicits.size()) {
2351            if (originalSize == 1) {
2352                if (uniqueValue != null) {
2353                    uniqueValue.value = values.iterator().next();
2354                }
2355                return KeywordStatus.UNIQUE;
2356            }
2357            return KeywordStatus.BOUNDED;
2358        }
2359
2360        // Compute if the quick test is insufficient.
2361
2362        HashSet<Double> subtractedSet = new HashSet<Double>(values);
2363        for (Double explicit : explicits) {
2364            subtractedSet.remove(explicit - offset);
2365        }
2366        if (subtractedSet.size() == 0) {
2367            return KeywordStatus.SUPPRESSED;
2368        }
2369
2370        if (uniqueValue != null && subtractedSet.size() == 1) {
2371            uniqueValue.value = subtractedSet.iterator().next();
2372        }
2373
2374        return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED;
2375    }
2376
2377    /**
2378     * @deprecated This API is ICU internal only.
2379     * @hide original deprecated declaration
2380     * @hide draft / provisional / internal are hidden on Android
2381     */
2382    @Deprecated
2383    public String getRules(String keyword) {
2384        return rules.getRules(keyword);
2385    }
2386
2387    private void writeObject(
2388            ObjectOutputStream out)
2389                    throws IOException {
2390        throw new NotSerializableException();
2391    }
2392
2393    private void readObject(ObjectInputStream in
2394            ) throws IOException, ClassNotFoundException {
2395        throw new NotSerializableException();
2396    }
2397
2398    private Object writeReplace() throws ObjectStreamException {
2399        return new PluralRulesSerialProxy(toString());
2400    }
2401
2402    /**
2403     * @deprecated internal
2404     * @hide original deprecated declaration
2405     * @hide draft / provisional / internal are hidden on Android
2406     */
2407    @Deprecated
2408    public int compareTo(PluralRules other) {
2409        return toString().compareTo(other.toString());
2410    }
2411
2412    /**
2413     * @deprecated internal
2414     * @hide original deprecated declaration
2415     * @hide draft / provisional / internal are hidden on Android
2416     */
2417    @Deprecated
2418    public Boolean isLimited(String keyword) {
2419        return rules.isLimited(keyword, SampleType.INTEGER);
2420    }
2421
2422    /**
2423     * @deprecated internal
2424     * @hide original deprecated declaration
2425     * @hide draft / provisional / internal are hidden on Android
2426     */
2427    @Deprecated
2428    public boolean isLimited(String keyword, SampleType sampleType) {
2429        return rules.isLimited(keyword, sampleType);
2430    }
2431
2432    /**
2433     * @deprecated internal
2434     * @hide original deprecated declaration
2435     * @hide draft / provisional / internal are hidden on Android
2436     */
2437    @Deprecated
2438    public boolean computeLimited(String keyword, SampleType sampleType) {
2439        return rules.computeLimited(keyword, sampleType);
2440    }
2441}
2442