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