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