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