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