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