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