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