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