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