2 *******************************************************************************
3 * Copyright (C) 2007-2013, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
8 package com.ibm.icu.text;
10 import java.io.IOException;
11 import java.io.NotSerializableException;
12 import java.io.ObjectInputStream;
13 import java.io.ObjectOutputStream;
14 import java.io.ObjectStreamException;
15 import java.io.Serializable;
16 import java.text.ParseException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Locale;
26 import java.util.TreeSet;
27 import java.util.regex.Pattern;
29 import com.ibm.icu.impl.PluralRulesLoader;
30 import com.ibm.icu.util.Output;
31 import com.ibm.icu.util.ULocale;
35 * Defines rules for mapping non-negative numeric values onto a small set of keywords.
38 * Rules are constructed from a text description, consisting of a series of keywords and conditions. The {@link #select}
39 * method examines each condition in order and returns the keyword for the first condition that matches the number. If
40 * none match, {@link #KEYWORD_OTHER} is returned.
43 * A PluralRules object is immutable. It contains caches for sample values, but those are synchronized.
45 * PluralRules is Serializable so that it can be used in formatters, which are serializable.
48 * For more information, details, and tips for writing rules, see the <a
49 * href="http://www.unicode.org/draft/reports/tr35/tr35.html#Language_Plural_Rules">LDML spec, C.11 Language Plural
57 * "one: n is 1; few: n in 2..4"
60 * This defines two rules, for 'one' and 'few'. The condition for 'one' is "n is 1" which means that the number must be
61 * equal to 1 for this condition to pass. The condition for 'few' is "n in 2..4" which means that the number must be
62 * between 2 and 4 inclusive - and be an integer - for this condition to pass. All other numbers are assigned the
63 * keyword "other" by the default rule.
67 * "zero: n is 0; one: n is 1; zero: n mod 100 in 1..19"
70 * This illustrates that the same keyword can be defined multiple times. Each rule is examined in order, and the first
71 * keyword whose condition passes is the one returned. Also notes that a modulus is applied to n in the last rule. Thus
72 * its condition holds for 119, 219, 319...
76 * "one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14"
79 * This illustrates conjunction and negation. The condition for 'few' has two parts, both of which must be met:
80 * "n mod 10 in 2..4" and "n mod 100 not in 12..14". The first part applies a modulus to n before the test as in the
81 * previous example. The second part applies a different modulus and also uses negation, thus it matches all numbers
82 * _not_ in 12, 13, 14, 112, 113, 114, 212, 213, 214...
88 * rules = rule (';' rule)*
89 * rule = keyword ':' condition
90 * keyword = <identifier>
91 * condition = and_condition ('or' and_condition)*
92 * and_condition = relation ('and' relation)*
93 * relation = not? expr not? rel not? range_list
94 * expr = ('n' | 'i' | 'f' | 'v' | 't') (mod value)?
96 * rel = 'in' | 'is' | '=' | '≠' | 'within'
98 * range_list = (range | value) (',' range_list)*
100 * digit = 0|1|2|3|4|5|6|7|8|9
101 * range = value'..'value
103 * <p>Each <b>not</b> term inverts the meaning; however, there should not be more than one of them.</p>
105 * The i, f, t, and v values are defined as follows:
108 * <li>i to be the integer digits.</li>
109 * <li>f to be the visible decimal digits, as an integer.</li>
110 * <li>t to be the visible decimal digits—without trailing zeros—as an integer.</li>
111 * <li>v to be the number of visible fraction digits.</li>
112 * <li>j is defined to only match integers. That is j is 3 fails if v != 0 (eg for 3.1 or 3.0).</li>
115 * Examples are in the following table:
117 * <table border='1' style="border-collapse:collapse">
128 * <td align="right">0</td>
134 * <td align="right">0</td>
140 * <td align="right">3</td>
146 * <td align="right">3</td>
152 * <td align="right">23</td>
158 * An "identifier" is a sequence of characters that do not have the Unicode Pattern_Syntax or Pattern_White_Space
161 * The difference between 'in' and 'within' is that 'in' only includes integers in the specified range, while 'within'
162 * includes all values. Using 'within' with a range_list consisting entirely of values is the same as using 'in' (it's
168 public class PluralRules implements Serializable {
170 static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze();
172 // TODO Remove RulesList by moving its API and fields into PluralRules.
175 * @deprecated This API is ICU internal only.
177 public static final String CATEGORY_SEPARATOR = "; ";
180 * @deprecated This API is ICU internal only.
182 public static final String KEYWORD_RULE_SEPARATOR = ": ";
184 private static final long serialVersionUID = 1;
186 private final RuleList rules;
187 private final transient Set<String> keywords;
190 * Provides a factory for returning plural rules
192 * @deprecated This API is ICU internal only.
195 public static abstract class Factory {
197 * Provides access to the predefined <code>PluralRules</code> for a given locale and the plural type.
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
204 * The locale for which a <code>PluralRules</code> object is returned.
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 * @deprecated This API is ICU internal only.
213 public abstract PluralRules forLocale(ULocale locale, PluralType type);
216 * Utility for getting CARDINAL rules.
217 * @param locale the locale
218 * @return plural rules.
219 * @deprecated This API is ICU internal only.
222 public final PluralRules forLocale(ULocale locale) {
223 return forLocale(locale, PluralType.CARDINAL);
227 * Returns the locales for which there is plurals data.
229 * @deprecated This API is ICU internal only.
232 public abstract ULocale[] getAvailableULocales();
235 * Returns the 'functionally equivalent' locale with respect to plural rules. Calling PluralRules.forLocale with
236 * the functionally equivalent locale, and with the provided locale, returns rules that behave the same. <br/>
237 * All locales with the same functionally equivalent locale have plural rules that behave the same. This is not
238 * exaustive; there may be other locales whose plural rules behave the same that do not have the same equivalent
242 * the locale to check
244 * if not null and of length > 0, this will hold 'true' at index 0 if locale is directly defined
245 * (without fallback) as having plural rules
246 * @return the functionally-equivalent locale
247 * @deprecated This API is ICU internal only.
250 public abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable);
253 * Returns the default factory.
254 * @deprecated This API is ICU internal only.
257 public static PluralRulesLoader getDefaultFactory() {
258 return PluralRulesLoader.loader;
262 * Returns whether or not there are overrides.
263 * @deprecated This API is ICU internal only.
266 public abstract boolean hasOverride(ULocale locale);
268 // Standard keywords.
271 * Common name for the 'zero' plural form.
274 public static final String KEYWORD_ZERO = "zero";
277 * Common name for the 'singular' plural form.
280 public static final String KEYWORD_ONE = "one";
283 * Common name for the 'dual' plural form.
286 public static final String KEYWORD_TWO = "two";
289 * Common name for the 'paucal' or other special plural form.
292 public static final String KEYWORD_FEW = "few";
295 * Common name for the arabic (11 to 99) plural form.
298 public static final String KEYWORD_MANY = "many";
301 * Common name for the default plural form. This name is returned
302 * for values to which no other form in the rule applies. It
303 * can additionally be assigned rules of its own.
306 public static final String KEYWORD_OTHER = "other";
309 * Value returned by {@link #getUniqueKeywordValue} when there is no
310 * unique value to return.
313 public static final double NO_UNIQUE_VALUE = -0.00123456777;
316 * Type of plurals and PluralRules.
319 public enum PluralType {
321 * Plural rules for cardinal numbers: 1 file vs. 2 files.
326 * Plural rules for ordinal numbers: 1st file, 2nd file, 3rd file, 4th file, etc.
333 * The default constraint that is always satisfied.
335 private static final Constraint NO_CONSTRAINT = new Constraint() {
336 private static final long serialVersionUID = 9163464945387899416L;
338 public boolean isFulfilled(FixedDecimal n) {
342 public boolean isLimited(SampleType sampleType) {
346 public String toString() {
354 private static final Rule DEFAULT_RULE = new Rule("other", NO_CONSTRAINT, null, null);
357 * Parses a plural rules description and returns a PluralRules.
358 * @param description the rule description.
359 * @throws ParseException if the description cannot be parsed.
360 * The exception index is typically not set, it will be -1.
363 public static PluralRules parseDescription(String description)
364 throws ParseException {
366 description = description.trim();
367 return description.length() == 0 ? DEFAULT : new PluralRules(parseRuleChain(description));
371 * Creates a PluralRules from a description if it is parsable,
372 * otherwise returns null.
373 * @param description the rule description.
374 * @return the PluralRules
377 public static PluralRules createRules(String description) {
379 return parseDescription(description);
380 } catch(Exception e) {
386 * The default rules that accept any number and return
387 * {@link #KEYWORD_OTHER}.
390 public static final PluralRules DEFAULT = new PluralRules(new RuleList().addRule(DEFAULT_RULE));
392 private enum Operand {
405 * @deprecated This API is ICU internal only.
407 public static class FixedDecimal extends Number implements Comparable<FixedDecimal> {
408 private static final long serialVersionUID = -4756200506571685661L;
411 * @deprecated This API is ICU internal only.
413 public final double source;
416 * @deprecated This API is ICU internal only.
418 public final int visibleDecimalDigitCount;
421 * @deprecated This API is ICU internal only.
423 public final int visibleDecimalDigitCountWithoutTrailingZeros;
426 * @deprecated This API is ICU internal only.
428 public final long decimalDigits;
431 * @deprecated This API is ICU internal only.
433 public final long decimalDigitsWithoutTrailingZeros;
436 * @deprecated This API is ICU internal only.
438 public final long integerValue;
441 * @deprecated This API is ICU internal only.
443 public final boolean hasIntegerValue;
446 * @deprecated This API is ICU internal only.
448 public final boolean isNegative;
449 private final int baseFactor;
453 * @deprecated This API is ICU internal only.
455 public double getSource() {
461 * @deprecated This API is ICU internal only.
463 public int getVisibleDecimalDigitCount() {
464 return visibleDecimalDigitCount;
469 * @deprecated This API is ICU internal only.
471 public int getVisibleDecimalDigitCountWithoutTrailingZeros() {
472 return visibleDecimalDigitCountWithoutTrailingZeros;
477 * @deprecated This API is ICU internal only.
479 public long getDecimalDigits() {
480 return decimalDigits;
485 * @deprecated This API is ICU internal only.
487 public long getDecimalDigitsWithoutTrailingZeros() {
488 return decimalDigitsWithoutTrailingZeros;
493 * @deprecated This API is ICU internal only.
495 public long getIntegerValue() {
501 * @deprecated This API is ICU internal only.
503 public boolean isHasIntegerValue() {
504 return hasIntegerValue;
509 * @deprecated This API is ICU internal only.
511 public boolean isNegative() {
517 * @deprecated This API is ICU internal only.
519 public int getBaseFactor() {
525 * @deprecated This API is ICU internal only.
526 * @param n is the original number
527 * @param v number of digits to the right of the decimal place. e.g 1.00 = 2 25. = 0
528 * @param f Corresponds to f in the plural rules grammar.
529 * The digits to the right of the decimal place as an integer. e.g 1.10 = 10
531 public FixedDecimal(double n, int v, long f) {
533 source = isNegative ? -n : n;
534 visibleDecimalDigitCount = v;
536 integerValue = (long)n;
537 hasIntegerValue = source == integerValue;
538 // check values. TODO make into unit test.
540 // long visiblePower = (int) Math.pow(10, v);
541 // if (fractionalDigits > visiblePower) {
542 // throw new IllegalArgumentException();
544 // double fraction = intValue + (fractionalDigits / (double) visiblePower);
545 // if (fraction != source) {
546 // double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
547 // if (diff > 0.00000001d) {
548 // throw new IllegalArgumentException();
552 decimalDigitsWithoutTrailingZeros = 0;
553 visibleDecimalDigitCountWithoutTrailingZeros = 0;
556 int trimmedCount = v;
557 while ((fdwtz%10) == 0) {
561 decimalDigitsWithoutTrailingZeros = fdwtz;
562 visibleDecimalDigitCountWithoutTrailingZeros = trimmedCount;
564 baseFactor = (int) Math.pow(10, v);
569 * @deprecated This API is ICU internal only.
571 public FixedDecimal(double n, int v) {
572 this(n,v,getFractionalDigits(n, v));
575 private static int getFractionalDigits(double n, int v) {
579 int baseFactor = (int) Math.pow(10, v);
580 long scaled = Math.round(n * baseFactor);
581 return (int) (scaled % baseFactor);
587 * @deprecated This API is ICU internal only.
589 public FixedDecimal(double n) {
590 this(n, decimals(n));
595 * @deprecated This API is ICU internal only.
597 public FixedDecimal(long n) {
603 * @deprecated This API is ICU internal only.
605 public static int decimals(double n) {
607 String temp = String.valueOf(n);
608 return temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1;
613 * @deprecated This API is ICU internal only.
615 public FixedDecimal (String n) {
616 // Ugly, but for samples we don't care.
617 this(Double.parseDouble(n), getVisibleFractionCount(n));
620 private static int getVisibleFractionCount(String value) {
621 value = value.trim();
622 int decimalPos = value.indexOf('.') + 1;
623 if (decimalPos == 0) {
626 return value.length() - decimalPos;
632 * @deprecated This API is ICU internal only.
634 public double get(Operand operand) {
636 default: return source;
637 case i: return integerValue;
638 case f: return decimalDigits;
639 case t: return decimalDigitsWithoutTrailingZeros;
640 case v: return visibleDecimalDigitCount;
641 case w: return visibleDecimalDigitCountWithoutTrailingZeros;
647 * @deprecated This API is ICU internal only.
649 public static Operand getOperand(String t) {
650 return Operand.valueOf(t);
654 * We're not going to care about NaN.
656 * @deprecated This API is ICU internal only.
658 public int compareTo(FixedDecimal other) {
659 if (integerValue != other.integerValue) {
660 return integerValue < other.integerValue ? -1 : 1;
662 if (source != other.source) {
663 return source < other.source ? -1 : 1;
665 if (visibleDecimalDigitCount != other.visibleDecimalDigitCount) {
666 return visibleDecimalDigitCount < other.visibleDecimalDigitCount ? -1 : 1;
668 long diff = decimalDigits - other.decimalDigits;
670 return diff < 0 ? -1 : 1;
677 * @deprecated This API is ICU internal only.
680 public boolean equals(Object arg0) {
687 if (!(arg0 instanceof FixedDecimal)) {
690 FixedDecimal other = (FixedDecimal)arg0;
691 return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount && decimalDigits == other.decimalDigits;
696 * @deprecated This API is ICU internal only.
699 public int hashCode() {
700 // TODO Auto-generated method stub
701 return (int)(decimalDigits + 37 * (visibleDecimalDigitCount + (int)(37 * source)));
706 * @deprecated This API is ICU internal only.
709 public String toString() {
710 return String.format("%." + visibleDecimalDigitCount + "f", source);
715 * @deprecated This API is ICU internal only.
717 public boolean hasIntegerValue() {
718 return hasIntegerValue;
723 * @deprecated This API is ICU internal only.
726 public int intValue() {
727 // TODO Auto-generated method stub
728 return (int)integerValue;
733 * @deprecated This API is ICU internal only.
736 public long longValue() {
742 * @deprecated This API is ICU internal only.
745 public float floatValue() {
746 return (float) source;
751 * @deprecated This API is ICU internal only.
754 public double doubleValue() {
760 * @deprecated This API is ICU internal only.
762 public long getShiftedValue() {
763 return integerValue * baseFactor + decimalDigits;
766 private void writeObject(
767 ObjectOutputStream out)
769 throw new NotSerializableException();
772 private void readObject(ObjectInputStream in
773 ) throws IOException, ClassNotFoundException {
774 throw new NotSerializableException();
779 * Selection parameter for either integer-only or decimal-only.
781 * @deprecated This API is ICU internal only.
783 public enum SampleType {INTEGER, DECIMAL}
786 * A range of NumberInfo that includes all values with the same visibleFractionDigitCount.
788 * @deprecated This API is ICU internal only.
790 public static class FixedDecimalRange {
793 * @deprecated This API is ICU internal only.
795 public final FixedDecimal start;
798 * @deprecated This API is ICU internal only.
800 public final FixedDecimal end;
803 * @deprecated This API is ICU internal only.
805 public FixedDecimalRange(FixedDecimal start, FixedDecimal end) {
806 if (start.visibleDecimalDigitCount != end.visibleDecimalDigitCount) {
807 throw new IllegalArgumentException("Ranges must have the same number of visible decimals: " + start + "~" + end);
814 * @deprecated This API is ICU internal only.
817 public String toString() {
818 return start + (end == start ? "" : "~" + end);
823 * A list of NumberInfo that includes all values with the same visibleFractionDigitCount.
825 * @deprecated This API is ICU internal only.
827 public static class FixedDecimalSamples {
830 * @deprecated This API is ICU internal only.
832 public final SampleType sampleType;
835 * @deprecated This API is ICU internal only.
837 public final Set<FixedDecimalRange> samples;
840 * @deprecated This API is ICU internal only.
842 public final boolean bounded;
844 * The samples must be immutable.
848 private FixedDecimalSamples(SampleType sampleType, Set<FixedDecimalRange> samples, boolean bounded) {
850 this.sampleType = sampleType;
851 this.samples = samples;
852 this.bounded = bounded;
855 * Parse a list of the form described in CLDR. The source must be trimmed.
857 static FixedDecimalSamples parse(String source) {
858 SampleType sampleType2;
859 boolean bounded2 = true;
860 boolean haveBound = false;
861 Set<FixedDecimalRange> samples2 = new LinkedHashSet<FixedDecimalRange>();
863 if (source.startsWith("integer")) {
864 sampleType2 = SampleType.INTEGER;
865 } else if (source.startsWith("decimal")) {
866 sampleType2 = SampleType.DECIMAL;
868 throw new IllegalArgumentException("Samples must start with 'integer' or 'decimal'");
870 source = source.substring(7).trim(); // remove both
872 for (String range : COMMA_SEPARATED.split(source)) {
873 if (range.equals("…") || range.equals("...")) {
879 throw new IllegalArgumentException("Can only have … at the end of samples: " + range);
881 String[] rangeParts = TILDE_SEPARATED.split(range);
882 switch (rangeParts.length) {
884 FixedDecimal sample = new FixedDecimal(rangeParts[0]);
885 checkDecimal(sampleType2, sample);
886 samples2.add(new FixedDecimalRange(sample, sample));
889 FixedDecimal start = new FixedDecimal(rangeParts[0]);
890 FixedDecimal end = new FixedDecimal(rangeParts[1]);
891 checkDecimal(sampleType2, start);
892 checkDecimal(sampleType2, end);
893 samples2.add(new FixedDecimalRange(start, end));
895 default: throw new IllegalArgumentException("Ill-formed number range: " + range);
898 return new FixedDecimalSamples(sampleType2, Collections.unmodifiableSet(samples2), bounded2);
901 private static void checkDecimal(SampleType sampleType2, FixedDecimal sample) {
902 if ((sampleType2 == SampleType.INTEGER) != (sample.getVisibleDecimalDigitCount() == 0)) {
903 throw new IllegalArgumentException("Ill-formed number range: " + sample);
909 * @deprecated This API is ICU internal only.
911 public Set<Double> addSamples(Set<Double> result) {
912 for (FixedDecimalRange item : samples) {
913 // we have to convert to longs so we don't get strange double issues
914 long startDouble = item.start.getShiftedValue();
915 long endDouble = item.end.getShiftedValue();
917 for (long d = startDouble; d <= endDouble; d += 1) {
918 result.add(d/(double)item.start.baseFactor);
926 * @deprecated This API is ICU internal only.
929 public String toString() {
930 StringBuilder b = new StringBuilder("@").append(sampleType.toString().toLowerCase(Locale.ENGLISH));
931 boolean first = true;
932 for (FixedDecimalRange item : samples) {
938 b.append(' ').append(item);
948 * @deprecated This API is ICU internal only.
950 public Set<FixedDecimalRange> getSamples() {
956 * @deprecated This API is ICU internal only.
958 public void getStartEndSamples(Set<FixedDecimal> target) {
959 for (FixedDecimalRange item : samples) {
960 target.add(item.start);
961 target.add(item.end);
967 * A constraint on a number.
969 private interface Constraint extends Serializable {
971 * Returns true if the number fulfills the constraint.
972 * @param n the number to test, >= 0.
974 boolean isFulfilled(FixedDecimal n);
977 * Returns false if an unlimited number of values fulfills the
980 boolean isLimited(SampleType sampleType);
983 static class SimpleTokenizer {
984 static final UnicodeSet BREAK_AND_IGNORE = new UnicodeSet(0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x20).freeze();
985 static final UnicodeSet BREAK_AND_KEEP = new UnicodeSet('!', '!', '%', '%', ',', ',', '.', '.', '=', '=').freeze();
986 static String[] split(String source) {
988 List<String> result = new ArrayList<String>();
989 for (int i = 0; i < source.length(); ++i) {
990 char ch = source.charAt(i);
991 if (BREAK_AND_IGNORE.contains(ch)) {
993 result.add(source.substring(last,i));
996 } else if (BREAK_AND_KEEP.contains(ch)) {
998 result.add(source.substring(last,i));
1000 result.add(source.substring(i,i+1));
1002 } else if (last < 0) {
1007 result.add(source.substring(last));
1009 return result.toArray(new String[result.size()]);
1015 * condition : or_condition
1017 * or_condition : and_condition 'or' condition
1018 * and_condition : relation
1019 * relation 'and' relation
1020 * relation : in_relation
1022 * in_relation : not? expr not? in not? range
1023 * within_relation : not? expr not? 'within' not? range
1035 * digit : 0|1|2|3|4|5|6|7|8|9
1036 * range : value'..'value
1038 private static Constraint parseConstraint(String description)
1039 throws ParseException {
1041 Constraint result = null;
1042 String[] or_together = OR_SEPARATED.split(description);
1043 for (int i = 0; i < or_together.length; ++i) {
1044 Constraint andConstraint = null;
1045 String[] and_together = AND_SEPARATED.split(or_together[i]);
1046 for (int j = 0; j < and_together.length; ++j) {
1047 Constraint newConstraint = NO_CONSTRAINT;
1049 String condition = and_together[j].trim();
1050 String[] tokens = SimpleTokenizer.split(condition);
1053 boolean inRange = true;
1054 boolean integersOnly = true;
1055 double lowBound = Long.MAX_VALUE;
1056 double highBound = Long.MIN_VALUE;
1060 String t = tokens[x++];
1061 boolean hackForCompatibility = false;
1064 operand = FixedDecimal.getOperand(t);
1065 } catch (Exception e) {
1066 throw unexpected(t, condition);
1068 if (x < tokens.length) {
1070 if ("mod".equals(t) || "%".equals(t)) {
1071 mod = Integer.parseInt(tokens[x++]);
1072 t = nextToken(tokens, x++, condition);
1074 if ("not".equals(t)) {
1076 t = nextToken(tokens, x++, condition);
1077 if ("=".equals(t)) {
1078 throw unexpected(t, condition);
1080 } else if ("!".equals(t)) {
1082 t = nextToken(tokens, x++, condition);
1083 if (!"=".equals(t)) {
1084 throw unexpected(t, condition);
1087 if ("is".equals(t) || "in".equals(t) || "=".equals(t)) {
1088 hackForCompatibility = "is".equals(t);
1089 if (hackForCompatibility && !inRange) {
1090 throw unexpected(t, condition);
1092 t = nextToken(tokens, x++, condition);
1093 } else if ("within".equals(t)) {
1094 integersOnly = false;
1095 t = nextToken(tokens, x++, condition);
1097 throw unexpected(t, condition);
1099 if ("not".equals(t)) {
1100 if (!hackForCompatibility && !inRange) {
1101 throw unexpected(t, condition);
1104 t = nextToken(tokens, x++, condition);
1107 List<Long> valueList = new ArrayList<Long>();
1109 // the token t is always one item ahead
1111 long low = Long.parseLong(t);
1113 if (x < tokens.length) {
1114 t = nextToken(tokens, x++, condition);
1115 if (t.equals(".")) {
1116 t = nextToken(tokens, x++, condition);
1117 if (!t.equals(".")) {
1118 throw unexpected(t, condition);
1120 t = nextToken(tokens, x++, condition);
1121 high = Long.parseLong(t);
1122 if (x < tokens.length) {
1123 t = nextToken(tokens, x++, condition);
1124 if (!t.equals(",")) { // adjacent number: 1 2
1125 // no separator, fail
1126 throw unexpected(t, condition);
1129 } else if (!t.equals(",")) { // adjacent number: 1 2
1130 // no separator, fail
1131 throw unexpected(t, condition);
1134 // at this point, either we are out of tokens, or t is ','
1136 throw unexpected(low + "~" + high, condition);
1137 } else if (mod != 0 && high >= mod) {
1138 throw unexpected(high + ">mod=" + mod, condition);
1141 valueList.add(high);
1142 lowBound = Math.min(lowBound, low);
1143 highBound = Math.max(highBound, high);
1144 if (x >= tokens.length) {
1147 t = nextToken(tokens, x++, condition);
1150 if (t.equals(",")) {
1151 throw unexpected(t, condition);
1154 if (valueList.size() == 2) {
1157 vals = new long[valueList.size()];
1158 for (int k = 0; k < vals.length; ++k) {
1159 vals[k] = valueList.get(k);
1163 // Hack to exclude "is not 1,2"
1164 if (lowBound != highBound && hackForCompatibility && !inRange) {
1165 throw unexpected("is not <range>", condition);
1169 new RangeConstraint(mod, inRange, operand, integersOnly, lowBound, highBound, vals);
1172 if (andConstraint == null) {
1173 andConstraint = newConstraint;
1175 andConstraint = new AndConstraint(andConstraint,
1180 if (result == null) {
1181 result = andConstraint;
1183 result = new OrConstraint(result, andConstraint);
1189 static final Pattern AT_SEPARATED = Pattern.compile("\\s*\\Q\\E@\\s*");
1190 static final Pattern OR_SEPARATED = Pattern.compile("\\s*or\\s*");
1191 static final Pattern AND_SEPARATED = Pattern.compile("\\s*and\\s*");
1192 static final Pattern COMMA_SEPARATED = Pattern.compile("\\s*,\\s*");
1193 static final Pattern DOTDOT_SEPARATED = Pattern.compile("\\s*\\Q..\\E\\s*");
1194 static final Pattern TILDE_SEPARATED = Pattern.compile("\\s*~\\s*");
1195 static final Pattern SEMI_SEPARATED = Pattern.compile("\\s*;\\s*");
1198 /* Returns a parse exception wrapping the token and context strings. */
1199 private static ParseException unexpected(String token, String context) {
1200 return new ParseException("unexpected token '" + token +
1201 "' in '" + context + "'", -1);
1205 * Returns the token at x if available, else throws a parse exception.
1207 private static String nextToken(String[] tokens, int x, String context)
1208 throws ParseException {
1209 if (x < tokens.length) {
1212 throw new ParseException("missing token at end of '" + context + "'", -1);
1217 * rule : keyword ':' condition
1218 * keyword: <identifier>
1220 private static Rule parseRule(String description) throws ParseException {
1221 if (description.length() == 0) {
1222 return DEFAULT_RULE;
1225 description = description.toLowerCase(Locale.ENGLISH);
1227 int x = description.indexOf(':');
1229 throw new ParseException("missing ':' in rule description '" +
1230 description + "'", 0);
1233 String keyword = description.substring(0, x).trim();
1234 if (!isValidKeyword(keyword)) {
1235 throw new ParseException("keyword '" + keyword +
1236 " is not valid", 0);
1239 description = description.substring(x+1).trim();
1240 String[] constraintOrSamples = AT_SEPARATED.split(description);
1241 boolean sampleFailure = false;
1242 FixedDecimalSamples integerSamples = null, decimalSamples = null;
1243 switch (constraintOrSamples.length) {
1246 integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
1247 if (integerSamples.sampleType == SampleType.DECIMAL) {
1248 decimalSamples = integerSamples;
1249 integerSamples = null;
1253 integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]);
1254 decimalSamples = FixedDecimalSamples.parse(constraintOrSamples[2]);
1255 if (integerSamples.sampleType != SampleType.INTEGER || decimalSamples.sampleType != SampleType.DECIMAL) {
1256 throw new IllegalArgumentException("Must have @integer then @decimal in " + description);
1260 throw new IllegalArgumentException("Too many samples in " + description);
1262 if (sampleFailure) {
1263 throw new IllegalArgumentException("Ill-formed samples—'@' characters.");
1266 // 'other' is special, and must have no rules; all other keywords must have rules.
1267 boolean isOther = keyword.equals("other");
1268 if (isOther != (constraintOrSamples[0].length() == 0)) {
1269 throw new IllegalArgumentException("The keyword 'other' must have no constraints, just samples.");
1272 Constraint constraint;
1274 constraint = NO_CONSTRAINT;
1276 constraint = parseConstraint(constraintOrSamples[0]);
1278 return new Rule(keyword, constraint, integerSamples, decimalSamples);
1287 private static RuleList parseRuleChain(String description)
1288 throws ParseException {
1289 RuleList result = new RuleList();
1290 // remove trailing ;
1291 if (description.endsWith(";")) {
1292 description = description.substring(0,description.length()-1);
1294 String[] rules = SEMI_SEPARATED.split(description);
1295 for (int i = 0; i < rules.length; ++i) {
1296 Rule rule = parseRule(rules[i].trim());
1297 result.hasExplicitBoundingInfo |= rule.integerSamples != null || rule.decimalSamples != null;
1298 result.addRule(rule);
1300 return result.finish();
1304 * An implementation of Constraint representing a modulus,
1305 * a range of values, and include/exclude. Provides lots of
1306 * convenience factory methods.
1308 private static class RangeConstraint implements Constraint, Serializable {
1309 private static final long serialVersionUID = 1;
1311 private final int mod;
1312 private final boolean inRange;
1313 private final boolean integersOnly;
1314 private final double lowerBound;
1315 private final double upperBound;
1316 private final long[] range_list;
1317 private final Operand operand;
1319 RangeConstraint(int mod, boolean inRange, Operand operand, boolean integersOnly,
1320 double lowBound, double highBound, long[] vals) {
1322 this.inRange = inRange;
1323 this.integersOnly = integersOnly;
1324 this.lowerBound = lowBound;
1325 this.upperBound = highBound;
1326 this.range_list = vals;
1327 this.operand = operand;
1330 public boolean isFulfilled(FixedDecimal number) {
1331 double n = number.get(operand);
1332 if ((integersOnly && (n - (long)n) != 0.0
1333 || operand == Operand.j && number.visibleDecimalDigitCount != 0)) {
1337 n = n % mod; // java % handles double numerator the way we want
1339 boolean test = n >= lowerBound && n <= upperBound;
1340 if (test && range_list != null) {
1342 for (int i = 0; !test && i < range_list.length; i += 2) {
1343 test = n >= range_list[i] && n <= range_list[i+1];
1346 return inRange == test;
1349 public boolean isLimited(SampleType sampleType) {
1350 boolean valueIsZero = lowerBound == upperBound && lowerBound == 0d;
1351 boolean hasDecimals =
1352 (operand == Operand.v || operand == Operand.w || operand == Operand.f || operand == Operand.t)
1353 && inRange != valueIsZero; // either NOT f = zero or f = non-zero
1354 switch (sampleType) {
1356 return hasDecimals // will be empty
1357 || (operand == Operand.n || operand == Operand.i || operand == Operand.j)
1362 return (!hasDecimals || operand == Operand.n || operand == Operand.j)
1363 && (integersOnly || lowerBound == upperBound)
1370 public String toString() {
1371 StringBuilder result = new StringBuilder();
1372 result.append(operand);
1374 result.append(" % ").append(mod);
1376 boolean isList = lowerBound != upperBound;
1378 !isList ? (inRange ? " = " : " != ")
1379 : integersOnly ? (inRange ? " = " : " != ")
1380 : (inRange ? " within " : " not within ")
1382 if (range_list != null) {
1383 for (int i = 0; i < range_list.length; i += 2) {
1384 addRange(result, range_list[i], range_list[i+1], i != 0);
1387 addRange(result, lowerBound, upperBound, false);
1389 return result.toString();
1393 private static void addRange(StringBuilder result, double lb, double ub, boolean addSeparator) {
1398 result.append(format(lb));
1400 result.append(format(lb) + ".." + format(ub));
1404 private static String format(double lb) {
1405 long lbi = (long) lb;
1406 return lb == lbi ? String.valueOf(lbi) : String.valueOf(lb);
1409 /* Convenience base class for and/or constraints. */
1410 private static abstract class BinaryConstraint implements Constraint,
1412 private static final long serialVersionUID = 1;
1413 protected final Constraint a;
1414 protected final Constraint b;
1416 protected BinaryConstraint(Constraint a, Constraint b) {
1422 /* A constraint representing the logical and of two constraints. */
1423 private static class AndConstraint extends BinaryConstraint {
1424 private static final long serialVersionUID = 7766999779862263523L;
1426 AndConstraint(Constraint a, Constraint b) {
1430 public boolean isFulfilled(FixedDecimal n) {
1431 return a.isFulfilled(n)
1432 && b.isFulfilled(n);
1435 public boolean isLimited(SampleType sampleType) {
1436 // we ignore the case where both a and b are unlimited but no values
1437 // satisfy both-- we still consider this 'unlimited'
1438 return a.isLimited(sampleType)
1439 || b.isLimited(sampleType);
1442 public String toString() {
1443 return a.toString() + " and " + b.toString();
1447 /* A constraint representing the logical or of two constraints. */
1448 private static class OrConstraint extends BinaryConstraint {
1449 private static final long serialVersionUID = 1405488568664762222L;
1451 OrConstraint(Constraint a, Constraint b) {
1455 public boolean isFulfilled(FixedDecimal n) {
1456 return a.isFulfilled(n)
1457 || b.isFulfilled(n);
1460 public boolean isLimited(SampleType sampleType) {
1461 return a.isLimited(sampleType)
1462 && b.isLimited(sampleType);
1465 public String toString() {
1466 return a.toString() + " or " + b.toString();
1471 * Implementation of Rule that uses a constraint.
1472 * Provides 'and' and 'or' to combine constraints. Immutable.
1474 private static class Rule implements Serializable {
1475 private static final long serialVersionUID = 1;
1476 private final String keyword;
1477 private final Constraint constraint;
1478 private final FixedDecimalSamples integerSamples;
1479 private final FixedDecimalSamples decimalSamples;
1481 public Rule(String keyword, Constraint constraint, FixedDecimalSamples integerSamples, FixedDecimalSamples decimalSamples) {
1482 this.keyword = keyword;
1483 this.constraint = constraint;
1484 this.integerSamples = integerSamples;
1485 this.decimalSamples = decimalSamples;
1488 @SuppressWarnings("unused")
1489 public Rule and(Constraint c) {
1490 return new Rule(keyword, new AndConstraint(constraint, c), integerSamples, decimalSamples);
1493 @SuppressWarnings("unused")
1494 public Rule or(Constraint c) {
1495 return new Rule(keyword, new OrConstraint(constraint, c), integerSamples, decimalSamples);
1498 public String getKeyword() {
1502 public boolean appliesTo(FixedDecimal n) {
1503 return constraint.isFulfilled(n);
1506 public boolean isLimited(SampleType sampleType) {
1507 return constraint.isLimited(sampleType);
1510 public String toString() {
1511 return keyword + ": " + constraint.toString()
1512 + (integerSamples == null ? "" : " " + integerSamples.toString())
1513 + (decimalSamples == null ? "" : " " + decimalSamples.toString());
1518 * @deprecated This API is ICU internal only.
1521 public int hashCode() {
1522 return keyword.hashCode() ^ constraint.hashCode();
1525 public String getConstraint() {
1526 return constraint.toString();
1530 private static class RuleList implements Serializable {
1531 private boolean hasExplicitBoundingInfo = false;
1532 private static final long serialVersionUID = 1;
1533 private final List<Rule> rules = new ArrayList<Rule>();
1535 public RuleList addRule(Rule nextRule) {
1536 String keyword = nextRule.getKeyword();
1537 for (Rule rule : rules) {
1538 if (keyword.equals(rule.getKeyword())) {
1539 throw new IllegalArgumentException("Duplicate keyword: " + keyword);
1542 rules.add(nextRule);
1546 public RuleList finish() throws ParseException {
1547 // make sure that 'other' is present, and at the end.
1548 Rule otherRule = null;
1549 for (Iterator<Rule> it = rules.iterator(); it.hasNext();) {
1550 Rule rule = it.next();
1551 if ("other".equals(rule.getKeyword())) {
1556 if (otherRule == null) {
1557 otherRule = parseRule("other:"); // make sure we have always have an 'other' a rule
1559 rules.add(otherRule);
1563 private Rule selectRule(FixedDecimal n) {
1564 for (Rule rule : rules) {
1565 if (rule.appliesTo(n)) {
1572 public String select(FixedDecimal n) {
1573 Rule r = selectRule(n);
1574 // since we have explict 'other', we don't need this.
1576 // return KEYWORD_OTHER;
1578 return r.getKeyword();
1581 public Set<String> getKeywords() {
1582 Set<String> result = new LinkedHashSet<String>();
1583 for (Rule rule : rules) {
1584 result.add(rule.getKeyword());
1586 // since we have explict 'other', we don't need this.
1587 //result.add(KEYWORD_OTHER);
1591 public boolean isLimited(String keyword, SampleType sampleType) {
1592 if (hasExplicitBoundingInfo) {
1593 FixedDecimalSamples mySamples = getDecimalSamples(keyword, sampleType);
1594 return mySamples == null ? true : mySamples.bounded;
1597 return computeLimited(keyword, sampleType);
1600 public boolean computeLimited(String keyword, SampleType sampleType) {
1601 // if all rules with this keyword are limited, it's limited,
1602 // and if there's no rule with this keyword, it's unlimited
1603 boolean result = false;
1604 for (Rule rule : rules) {
1605 if (keyword.equals(rule.getKeyword())) {
1606 if (!rule.isLimited(sampleType)) {
1615 public String toString() {
1616 StringBuilder builder = new StringBuilder();
1617 for (Rule rule : rules) {
1618 if (builder.length() != 0) {
1619 builder.append(CATEGORY_SEPARATOR);
1621 builder.append(rule);
1623 return builder.toString();
1626 public String getRules(String keyword) {
1627 for (Rule rule : rules) {
1628 if (rule.getKeyword().equals(keyword)) {
1629 return rule.getConstraint();
1635 public boolean select(FixedDecimal sample, String keyword) {
1636 for (Rule rule : rules) {
1637 if (rule.getKeyword().equals(keyword) && rule.appliesTo(sample)) {
1644 public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
1645 for (Rule rule : rules) {
1646 if (rule.getKeyword().equals(keyword)) {
1647 return sampleType == SampleType.INTEGER ? rule.integerSamples : rule.decimalSamples;
1655 * @deprecated This API is ICU internal only.
1658 public enum StandardPluralCategories {
1665 static StandardPluralCategories forString(String s) {
1666 StandardPluralCategories a;
1669 } catch (Exception e) {
1676 @SuppressWarnings("unused")
1677 private boolean addConditional(Set<FixedDecimal> toAddTo, Set<FixedDecimal> others, double trial) {
1679 FixedDecimal toAdd = new FixedDecimal(trial);
1680 if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) {
1691 // -------------------------------------------------------------------------
1692 // Static class methods.
1693 // -------------------------------------------------------------------------
1696 * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given
1698 * Same as forLocale(locale, PluralType.CARDINAL).
1700 * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1701 * For these predefined rules, see CLDR page at
1702 * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1704 * @param locale The locale for which a <code>PluralRules</code> object is
1706 * @return The predefined <code>PluralRules</code> object for this locale.
1707 * If there's no predefined rules for this locale, the rules
1708 * for the closest parent in the locale hierarchy that has one will
1709 * be returned. The final fallback always returns the default
1713 public static PluralRules forLocale(ULocale locale) {
1714 return Factory.getDefaultFactory().forLocale(locale, PluralType.CARDINAL);
1718 * Provides access to the predefined <code>PluralRules</code> for a given
1719 * locale and the plural type.
1721 * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>.
1722 * For these predefined rules, see CLDR page at
1723 * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
1725 * @param locale The locale for which a <code>PluralRules</code> object is
1727 * @param type The plural type (e.g., cardinal or ordinal).
1728 * @return The predefined <code>PluralRules</code> object for this locale.
1729 * If there's no predefined rules for this locale, the rules
1730 * for the closest parent in the locale hierarchy that has one will
1731 * be returned. The final fallback always returns the default
1735 public static PluralRules forLocale(ULocale locale, PluralType type) {
1736 return Factory.getDefaultFactory().forLocale(locale, type);
1740 * Checks whether a token is a valid keyword.
1742 * @param token the token to be checked
1743 * @return true if the token is a valid keyword.
1745 private static boolean isValidKeyword(String token) {
1746 return ALLOWED_ID.containsAll(token);
1750 * Creates a new <code>PluralRules</code> object. Immutable.
1752 private PluralRules(RuleList rules) {
1754 this.keywords = Collections.unmodifiableSet(rules.getKeywords());
1759 * @deprecated This API is ICU internal only.
1762 public int hashCode() {
1763 return rules.hashCode();
1766 * Given a number, returns the keyword of the first rule that applies to
1769 * @param number The number for which the rule has to be determined.
1770 * @return The keyword of the selected rule.
1773 public String select(double number) {
1774 return rules.select(new FixedDecimal(number));
1778 * Given a number, returns the keyword of the first rule that applies to
1781 * @param number The number for which the rule has to be determined.
1782 * @return The keyword of the selected rule.
1784 * @deprecated This API is ICU internal only.
1786 public String select(double number, int countVisibleFractionDigits, long fractionaldigits) {
1787 return rules.select(new FixedDecimal(number, countVisibleFractionDigits, fractionaldigits));
1791 * Given a number information, returns the keyword of the first rule that applies to
1794 * @param sample The number information for which the rule has to be determined.
1795 * @return The keyword of the selected rule.
1797 * @deprecated This API is ICU internal only.
1799 public String select(FixedDecimal sample) {
1800 return rules.select(sample);
1805 * Given a number information, and keyword, return whether the keyword would match the number.
1807 * @param sample The number information for which the rule has to be determined.
1808 * @param keyword The keyword to filter on
1810 * @deprecated This API is ICU internal only.
1812 public boolean matches(FixedDecimal sample, String keyword) {
1813 return rules.select(sample, keyword);
1817 * Returns a set of all rule keywords used in this <code>PluralRules</code>
1818 * object. The rule "other" is always present by default.
1820 * @return The set of keywords.
1823 public Set<String> getKeywords() {
1828 * Returns the unique value that this keyword matches, or {@link #NO_UNIQUE_VALUE}
1829 * if the keyword matches multiple values or is not defined for this PluralRules.
1831 * @param keyword the keyword to check for a unique value
1832 * @return The unique value for the keyword, or NO_UNIQUE_VALUE.
1835 public double getUniqueKeywordValue(String keyword) {
1836 Collection<Double> values = getAllKeywordValues(keyword);
1837 if (values != null && values.size() == 1) {
1838 return values.iterator().next();
1840 return NO_UNIQUE_VALUE;
1844 * Returns all the values that trigger this keyword, or null if the number of such
1845 * values is unlimited.
1847 * @param keyword the keyword
1848 * @return the values that trigger this keyword, or null. The returned collection
1849 * is immutable. It will be empty if the keyword is not defined.
1852 public Collection<Double> getAllKeywordValues(String keyword) {
1853 return getAllKeywordValues(keyword, SampleType.INTEGER);
1857 * Returns all the values that trigger this keyword, or null if the number of such
1858 * values is unlimited.
1860 * @param keyword the keyword
1861 * @return the values that trigger this keyword, or null. The returned collection
1862 * is immutable. It will be empty if the keyword is not defined.
1865 * @deprecated This API is ICU internal only.
1867 public Collection<Double> getAllKeywordValues(String keyword, SampleType type) {
1868 if (!isLimited(keyword, type)) {
1871 Collection<Double> samples = getSamples(keyword, type);
1872 return samples == null ? null : Collections.unmodifiableCollection(samples);
1876 * Returns a list of values for which select() would return that keyword,
1877 * or null if the keyword is not defined. The returned collection is unmodifiable.
1878 * The returned list is not complete, and there might be additional values that
1879 * would return the keyword.
1881 * @param keyword the keyword to test
1882 * @return a list of values matching the keyword.
1885 public Collection<Double> getSamples(String keyword) {
1886 return getSamples(keyword, SampleType.INTEGER);
1890 * Returns a list of values for which select() would return that keyword,
1891 * or null if the keyword is not defined.
1892 * The returned collection is unmodifiable.
1893 * The returned list is not complete, and there might be additional values that
1894 * would return the keyword. The keyword might be defined, and yet have an empty set of samples,
1895 * IF there are samples for the other sampleType.
1897 * @param keyword the keyword to test
1898 * @return a list of values matching the keyword.
1899 * @deprecated ICU internal only
1902 public Collection<Double> getSamples(String keyword, SampleType sampleType) {
1903 if (!keywords.contains(keyword)) {
1906 Set<Double> result = new TreeSet<Double>();
1908 if (rules.hasExplicitBoundingInfo) {
1909 FixedDecimalSamples samples = rules.getDecimalSamples(keyword, sampleType);
1910 return samples == null ? Collections.unmodifiableSet(result)
1911 : Collections.unmodifiableSet(samples.addSamples(result));
1914 // hack in case the rule is created without explicit samples
1915 int maxCount = isLimited(keyword, sampleType) ? Integer.MAX_VALUE : 20;
1917 switch (sampleType) {
1919 for (int i = 0; i < 200; ++i) {
1920 if (!addSample(keyword, i, maxCount, result)) {
1924 addSample(keyword, 1000000, maxCount, result); // hack for Welsh
1927 for (int i = 0; i < 2000; ++i) {
1928 if (!addSample(keyword, new FixedDecimal(i/10d, 1), maxCount, result)) {
1932 addSample(keyword, new FixedDecimal(1000000d, 1), maxCount, result); // hack for Welsh
1935 return result.size() == 0 ? null : Collections.unmodifiableSet(result);
1940 * @deprecated This API is ICU internal only.
1942 public boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) {
1943 String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue());
1944 if (selectedKeyword.equals(keyword)) {
1945 result.add(sample.doubleValue());
1946 if (--maxCount < 0) {
1954 * Returns a list of values for which select() would return that keyword,
1955 * or null if the keyword is not defined or no samples are available.
1956 * The returned collection is unmodifiable.
1957 * The returned list is not complete, and there might be additional values that
1958 * would return the keyword.
1960 * @param keyword the keyword to test
1961 * @return a list of values matching the keyword.
1963 * @deprecated This API is ICU internal only.
1965 public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) {
1966 return rules.getDecimalSamples(keyword, sampleType);
1970 * Returns the set of locales for which PluralRules are known.
1971 * @return the set of locales for which PluralRules are known, as a list
1973 * @provisional This API might change or be removed in a future release.
1975 public static ULocale[] getAvailableULocales() {
1976 return Factory.getDefaultFactory().getAvailableULocales();
1980 * Returns the 'functionally equivalent' locale with respect to
1981 * plural rules. Calling PluralRules.forLocale with the functionally equivalent
1982 * locale, and with the provided locale, returns rules that behave the same.
1984 * All locales with the same functionally equivalent locale have
1985 * plural rules that behave the same. This is not exaustive;
1986 * there may be other locales whose plural rules behave the same
1987 * that do not have the same equivalent locale.
1989 * @param locale the locale to check
1990 * @param isAvailable if not null and of length > 0, this will hold 'true' at
1991 * index 0 if locale is directly defined (without fallback) as having plural rules
1992 * @return the functionally-equivalent locale
1994 * @provisional This API might change or be removed in a future release.
1996 public static ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
1997 return Factory.getDefaultFactory().getFunctionalEquivalent(locale, isAvailable);
2004 public String toString() {
2005 return rules.toString();
2012 public boolean equals(Object rhs) {
2013 return rhs instanceof PluralRules && equals((PluralRules)rhs);
2017 * Returns true if rhs is equal to this.
2018 * @param rhs the PluralRules to compare to.
2019 * @return true if this and rhs are equal.
2022 // TODO Optimize this
2023 public boolean equals(PluralRules rhs) {
2024 return rhs != null && toString().equals(rhs.toString());
2028 * Status of the keyword for the rules, given a set of explicit values.
2031 * @provisional This API might change or be removed in a future release.
2033 public enum KeywordStatus {
2035 * The keyword is not valid for the rules.
2038 * @provisional This API might change or be removed in a future release.
2042 * The keyword is valid, but unused (it is covered by the explicit values, OR has no values for the given {@link SampleType}).
2045 * @provisional This API might change or be removed in a future release.
2049 * The keyword is valid, used, and has a single possible value (before considering explicit values).
2052 * @provisional This API might change or be removed in a future release.
2056 * The keyword is valid, used, not unique, and has a finite set of values.
2059 * @provisional This API might change or be removed in a future release.
2063 * The keyword is valid but not bounded; there indefinitely many matching values.
2066 * @provisional This API might change or be removed in a future release.
2072 * Find the status for the keyword, given a certain set of explicit values.
2075 * the particular keyword (call rules.getKeywords() to get the valid ones)
2077 * the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
2078 * checking against the keyword values.
2080 * a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
2081 * @param uniqueValue
2082 * If non null, set to the unique value.
2083 * @return the KeywordStatus
2085 * @provisional This API might change or be removed in a future release.
2087 public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
2088 Output<Double> uniqueValue) {
2089 return getKeywordStatus(keyword, offset, explicits, uniqueValue, SampleType.INTEGER);
2092 * Find the status for the keyword, given a certain set of explicit values.
2095 * the particular keyword (call rules.getKeywords() to get the valid ones)
2097 * the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before
2098 * checking against the keyword values.
2100 * a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null.
2101 * @param uniqueValue
2102 * If non null, set to the unique value.
2103 * @return the KeywordStatus
2105 * @provisional This API might change or be removed in a future release.
2107 public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits,
2108 Output<Double> uniqueValue, SampleType sampleType) {
2109 if (uniqueValue != null) {
2110 uniqueValue.value = null;
2113 if (!keywords.contains(keyword)) {
2114 return KeywordStatus.INVALID;
2117 if (!isLimited(keyword, sampleType)) {
2118 return KeywordStatus.UNBOUNDED;
2121 Collection<Double> values = getSamples(keyword, sampleType);
2123 int originalSize = values.size();
2125 if (explicits == null) {
2126 explicits = Collections.emptySet();
2129 // Quick check on whether there are multiple elements
2131 if (originalSize > explicits.size()) {
2132 if (originalSize == 1) {
2133 if (uniqueValue != null) {
2134 uniqueValue.value = values.iterator().next();
2136 return KeywordStatus.UNIQUE;
2138 return KeywordStatus.BOUNDED;
2141 // Compute if the quick test is insufficient.
2143 HashSet<Double> subtractedSet = new HashSet<Double>(values);
2144 for (Double explicit : explicits) {
2145 subtractedSet.remove(explicit - offset);
2147 if (subtractedSet.size() == 0) {
2148 return KeywordStatus.SUPPRESSED;
2151 if (uniqueValue != null && subtractedSet.size() == 1) {
2152 uniqueValue.value = subtractedSet.iterator().next();
2155 return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED;
2160 * @deprecated This API is ICU internal only.
2162 public String getRules(String keyword) {
2163 return rules.getRules(keyword);
2166 private void writeObject(
2167 ObjectOutputStream out)
2168 throws IOException {
2169 throw new NotSerializableException();
2172 private void readObject(ObjectInputStream in
2173 ) throws IOException, ClassNotFoundException {
2174 throw new NotSerializableException();
2177 private Object writeReplace() throws ObjectStreamException {
2178 return new PluralRulesSerialProxy(toString());
2183 * @deprecated internal
2185 public int compareTo(PluralRules other) {
2186 return toString().compareTo(other.toString());
2191 * @deprecated internal
2193 public Boolean isLimited(String keyword) {
2194 return rules.isLimited(keyword, SampleType.INTEGER);
2199 * @deprecated internal
2201 public boolean isLimited(String keyword, SampleType sampleType) {
2202 return rules.isLimited(keyword, sampleType);
2207 * @deprecated internal
2209 public boolean computeLimited(String keyword, SampleType sampleType) {
2210 return rules.computeLimited(keyword, sampleType);