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