1 /* 2 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.text; 26 27 import java.io.IOException; 28 import java.io.InvalidObjectException; 29 import java.io.ObjectInputStream; 30 import java.math.BigDecimal; 31 import java.math.BigInteger; 32 import java.math.RoundingMode; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Locale; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.concurrent.atomic.AtomicInteger; 41 import java.util.concurrent.atomic.AtomicLong; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import java.util.stream.Collectors; 45 46 47 /** 48 * <p> 49 * {@code CompactNumberFormat} is a concrete subclass of {@code NumberFormat} 50 * that formats a decimal number in its compact form. 51 * 52 * The compact number formatting is designed for the environment where the space 53 * is limited, and the formatted string can be displayed in that limited space. 54 * It is defined by LDML's specification for 55 * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats"> 56 * Compact Number Formats</a>. A compact number formatting refers 57 * to the representation of a number in a shorter form, based on the patterns 58 * provided for a given locale. 59 * 60 * <p> 61 * For example: 62 * <br>In the {@link java.util.Locale#US US locale}, {@code 1000} can be formatted 63 * as {@code "1K"}, and {@code 1000000} as {@code "1M"}, depending upon the 64 * <a href = "#compact_number_style" >style</a> used. 65 * <br>In the {@code "hi_IN"} locale, {@code 1000} can be formatted as 66 * "1 \u0939\u091C\u093C\u093E\u0930", and {@code 50000000} as "5 \u0915.", 67 * depending upon the <a href = "#compact_number_style" >style</a> used. 68 * 69 * <p> 70 * To obtain a {@code CompactNumberFormat} for a locale, use one 71 * of the factory methods given by {@code NumberFormat} for compact number 72 * formatting. For example, 73 * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. 74 * 75 * <blockquote><pre> 76 * NumberFormat fmt = NumberFormat.getCompactNumberInstance( 77 * new Locale("hi", "IN"), NumberFormat.Style.SHORT); 78 * String result = fmt.format(1000); 79 * </pre></blockquote> 80 * 81 * <h2><a id="compact_number_style">Style</a></h2> 82 * <p> 83 * A number can be formatted in the compact forms with two different 84 * styles, {@link NumberFormat.Style#SHORT SHORT} 85 * and {@link NumberFormat.Style#LONG LONG}. Use 86 * {@link NumberFormat#getCompactNumberInstance(Locale, Style)} for formatting and 87 * parsing a number in {@link NumberFormat.Style#SHORT SHORT} or 88 * {@link NumberFormat.Style#LONG LONG} compact form, 89 * where the given {@code Style} parameter requests the desired 90 * format. A {@link NumberFormat.Style#SHORT SHORT} style 91 * compact number instance in the {@link java.util.Locale#US US locale} formats 92 * {@code 10000} as {@code "10K"}. However, a 93 * {@link NumberFormat.Style#LONG LONG} style instance in same locale 94 * formats {@code 10000} as {@code "10 thousand"}. 95 * 96 * <h2><a id="compact_number_patterns">Compact Number Patterns</a></h2> 97 * <p> 98 * The compact number patterns are represented in a series of patterns where each 99 * pattern is used to format a range of numbers. An example of 100 * {@link NumberFormat.Style#SHORT SHORT} styled compact number patterns 101 * for the {@link java.util.Locale#US US locale} is {@code {"", "", "", "0K", 102 * "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"}}, 103 * ranging from {@code 10}<sup>{@code 0}</sup> to {@code 10}<sup>{@code 14}</sup>. 104 * There can be any number of patterns and they are 105 * strictly index based starting from the range {@code 10}<sup>{@code 0}</sup>. 106 * For example, in the above patterns, pattern at index 3 107 * ({@code "0K"}) is used for formatting {@code number >= 1000 and number < 10000}, 108 * pattern at index 4 ({@code "00K"}) is used for formatting 109 * {@code number >= 10000 and number < 100000} and so on. In most of the locales, 110 * patterns with the range 111 * {@code 10}<sup>{@code 0}</sup>-{@code 10}<sup>{@code 2}</sup> are empty 112 * strings, which implicitly means a special pattern {@code "0"}. 113 * A special pattern {@code "0"} is used for any range which does not contain 114 * a compact pattern. This special pattern can appear explicitly for any specific 115 * range, or considered as a default pattern for an empty string. 116 * 117 * <p> 118 * A compact pattern contains a positive and negative subpattern 119 * separated by a subpattern boundary character {@code ';' (U+003B)}, 120 * for example, {@code "0K;-0K"}. Each subpattern has a prefix, 121 * minimum integer digits, and suffix. The negative subpattern 122 * is optional, if absent, then the positive subpattern prefixed with the 123 * minus sign ({@code '-' U+002D HYPHEN-MINUS}) is used as the negative 124 * subpattern. That is, {@code "0K"} alone is equivalent to {@code "0K;-0K"}. 125 * If there is an explicit negative subpattern, it serves only to specify 126 * the negative prefix and suffix. The number of minimum integer digits, 127 * and other characteristics are all the same as the positive pattern. 128 * That means that {@code "0K;-00K"} produces precisely the same behavior 129 * as {@code "0K;-0K"}. 130 * 131 * <p> 132 * Many characters in a compact pattern are taken literally, they are matched 133 * during parsing and output unchanged during formatting. 134 * <a href = "DecimalFormat.html#special_pattern_character">Special characters</a>, 135 * on the other hand, stand for other characters, strings, or classes of 136 * characters. They must be quoted, using single quote {@code ' (U+0027)} 137 * unless noted otherwise, if they are to appear in the prefix or suffix 138 * as literals. For example, 0\u0915'.'. 139 * 140 * <h3>Plurals</h3> 141 * <p> 142 * In case some localization requires compact number patterns to be different for 143 * plurals, each singular and plural pattern can be enumerated within a pair of 144 * curly brackets <code>'{' (U+007B)</code> and <code>'}' (U+007D)</code>, separated 145 * by a space {@code ' ' (U+0020)}. If this format is used, each pattern needs to be 146 * prepended by its {@code count}, followed by a single colon {@code ':' (U+003A)}. 147 * If the pattern includes spaces literally, they must be quoted. 148 * <p> 149 * For example, the compact number pattern representing millions in German locale can be 150 * specified as {@code "{one:0' 'Million other:0' 'Millionen}"}. The {@code count} 151 * follows LDML's 152 * <a href="https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules"> 153 * Language Plural Rules</a>. 154 * <p> 155 * A compact pattern has the following syntax: 156 * <blockquote><pre> 157 * <i>Pattern:</i> 158 * <i>SimplePattern</i> 159 * '{' <i>PluralPattern</i> <i>[' ' PluralPattern]<sub>optional</sub></i> '}' 160 * <i>SimplePattern:</i> 161 * <i>PositivePattern</i> 162 * <i>PositivePattern</i> <i>[; NegativePattern]<sub>optional</sub></i> 163 * <i>PluralPattern:</i> 164 * <i>Count</i>:<i>SimplePattern</i> 165 * <i>Count:</i> 166 * "zero" / "one" / "two" / "few" / "many" / "other" 167 * <i>PositivePattern:</i> 168 * <i>Prefix<sub>optional</sub></i> <i>MinimumInteger</i> <i>Suffix<sub>optional</sub></i> 169 * <i>NegativePattern:</i> 170 * <i>Prefix<sub>optional</sub></i> <i>MinimumInteger</i> <i>Suffix<sub>optional</sub></i> 171 * <i>Prefix:</i> 172 * Any Unicode characters except \uFFFE, \uFFFF, and 173 * <a href = "DecimalFormat.html#special_pattern_character">special characters</a>. 174 * <i>Suffix:</i> 175 * Any Unicode characters except \uFFFE, \uFFFF, and 176 * <a href = "DecimalFormat.html#special_pattern_character">special characters</a>. 177 * <i>MinimumInteger:</i> 178 * 0 179 * 0 <i>MinimumInteger</i> 180 * </pre></blockquote> 181 * 182 * <h2>Formatting</h2> 183 * The default formatting behavior returns a formatted string with no fractional 184 * digits, however users can use the {@link #setMinimumFractionDigits(int)} 185 * method to include the fractional part. 186 * The number {@code 1000.0} or {@code 1000} is formatted as {@code "1K"} 187 * not {@code "1.00K"} (in the {@link java.util.Locale#US US locale}). For this 188 * reason, the patterns provided for formatting contain only the minimum 189 * integer digits, prefix and/or suffix, but no fractional part. 190 * For example, patterns used are {@code {"", "", "", 0K, 00K, ...}}. If the pattern 191 * selected for formatting a number is {@code "0"} (special pattern), 192 * either explicit or defaulted, then the general number formatting provided by 193 * {@link java.text.DecimalFormat DecimalFormat} 194 * for the specified locale is used. 195 * 196 * <h2>Parsing</h2> 197 * The default parsing behavior does not allow a grouping separator until 198 * grouping used is set to {@code true} by using 199 * {@link #setGroupingUsed(boolean)}. The parsing of the fractional part 200 * depends on the {@link #isParseIntegerOnly()}. For example, if the 201 * parse integer only is set to true, then the fractional part is skipped. 202 * 203 * <h2>Rounding</h2> 204 * {@code CompactNumberFormat} provides rounding modes defined in 205 * {@link java.math.RoundingMode} for formatting. By default, it uses 206 * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. 207 * 208 * @see NumberFormat.Style 209 * @see NumberFormat 210 * @see DecimalFormat 211 * @since 12 212 */ 213 public final class CompactNumberFormat extends NumberFormat { 214 215 @java.io.Serial 216 private static final long serialVersionUID = 7128367218649234678L; 217 218 /** 219 * The patterns for compact form of numbers for this 220 * {@code CompactNumberFormat}. A possible example is 221 * {@code {"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B", 222 * "00B", "000B", "0T", "00T", "000T"}} ranging from 223 * {@code 10}<sup>{@code 0}</sup>-{@code 10}<sup>{@code 14}</sup>, 224 * where each pattern is used to format a range of numbers. 225 * For example, {@code "0K"} is used for formatting 226 * {@code number >= 1000 and number < 10000}, {@code "00K"} is used for 227 * formatting {@code number >= 10000 and number < 100000} and so on. 228 * This field must not be {@code null}. 229 * 230 * @serial 231 */ 232 private String[] compactPatterns; 233 234 /** 235 * List of positive prefix patterns of this formatter's 236 * compact number patterns. 237 */ 238 private transient List<Patterns> positivePrefixPatterns; 239 240 /** 241 * List of negative prefix patterns of this formatter's 242 * compact number patterns. 243 */ 244 private transient List<Patterns> negativePrefixPatterns; 245 246 /** 247 * List of positive suffix patterns of this formatter's 248 * compact number patterns. 249 */ 250 private transient List<Patterns> positiveSuffixPatterns; 251 252 /** 253 * List of negative suffix patterns of this formatter's 254 * compact number patterns. 255 */ 256 private transient List<Patterns> negativeSuffixPatterns; 257 258 /** 259 * List of divisors of this formatter's compact number patterns. 260 * Divisor can be either Long or BigInteger (if the divisor value goes 261 * beyond long boundary) 262 */ 263 private transient List<Number> divisors; 264 265 /** 266 * List of place holders that represent minimum integer digits at each index 267 * for each count. 268 */ 269 private transient List<Patterns> placeHolders; 270 271 /** 272 * The {@code DecimalFormatSymbols} object used by this format. 273 * It contains the symbols used to format numbers. For example, 274 * the grouping separator, decimal separator, and so on. 275 * This field must not be {@code null}. 276 * 277 * @serial 278 * @see DecimalFormatSymbols 279 */ 280 private DecimalFormatSymbols symbols; 281 282 /** 283 * The decimal pattern which is used for formatting the numbers 284 * matching special pattern "0". This field must not be {@code null}. 285 * 286 * @serial 287 * @see DecimalFormat 288 */ 289 private final String decimalPattern; 290 291 /** 292 * A {@code DecimalFormat} used by this format for getting corresponding 293 * general number formatting behavior for compact numbers. 294 * 295 */ 296 private transient DecimalFormat decimalFormat; 297 298 /** 299 * A {@code DecimalFormat} used by this format for getting general number 300 * formatting behavior for the numbers which can't be represented as compact 301 * numbers. For example, number matching the special pattern "0" are 302 * formatted through general number format pattern provided by 303 * {@link java.text.DecimalFormat DecimalFormat} 304 * for the specified locale. 305 * 306 */ 307 private transient DecimalFormat defaultDecimalFormat; 308 309 /** 310 * The number of digits between grouping separators in the integer portion 311 * of a compact number. For the grouping to work while formatting, this 312 * field needs to be greater than 0 with grouping used set as true. 313 * This field must not be negative. 314 * 315 * @serial 316 */ 317 private byte groupingSize = 0; 318 319 /** 320 * Returns whether the {@link #parse(String, ParsePosition)} 321 * method returns {@code BigDecimal}. 322 * 323 * @serial 324 */ 325 private boolean parseBigDecimal = false; 326 327 /** 328 * The {@code RoundingMode} used in this compact number format. 329 * This field must not be {@code null}. 330 * 331 * @serial 332 */ 333 private RoundingMode roundingMode = RoundingMode.HALF_EVEN; 334 335 /** 336 * The {@code pluralRules} used in this compact number format. 337 * {@code pluralRules} is a String designating plural rules which associate 338 * the {@code Count} keyword, such as "{@code one}", and the 339 * actual integer number. Its syntax is defined in Unicode Consortium's 340 * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax"> 341 * Plural rules syntax</a>. 342 * The default value is an empty string, meaning there is no plural rules. 343 * 344 * @serial 345 * @since 14 346 */ 347 private String pluralRules = ""; 348 349 /** 350 * The map for plural rules that maps LDML defined tags (e.g. "one") to 351 * its rule. 352 */ 353 private transient Map<String, String> rulesMap; 354 355 /** 356 * Special pattern used for compact numbers 357 */ 358 private static final String SPECIAL_PATTERN = "0"; 359 360 /** 361 * Multiplier for compact pattern range. In 362 * the list compact patterns each compact pattern 363 * specify the range with the multiplication factor of 10 364 * of its previous compact pattern range. 365 * For example, 10^0, 10^1, 10^2, 10^3, 10^4... 366 * 367 */ 368 private static final int RANGE_MULTIPLIER = 10; 369 370 /** 371 * Creates a {@code CompactNumberFormat} using the given decimal pattern, 372 * decimal format symbols and compact patterns. 373 * To obtain the instance of {@code CompactNumberFormat} with the standard 374 * compact patterns for a {@code Locale} and {@code Style}, 375 * it is recommended to use the factory methods given by 376 * {@code NumberFormat} for compact number formatting. For example, 377 * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. 378 * 379 * @param decimalPattern a decimal pattern for general number formatting 380 * @param symbols the set of symbols to be used 381 * @param compactPatterns an array of 382 * <a href = "CompactNumberFormat.html#compact_number_patterns"> 383 * compact number patterns</a> 384 * @throws NullPointerException if any of the given arguments is 385 * {@code null} 386 * @throws IllegalArgumentException if the given {@code decimalPattern} or the 387 * {@code compactPatterns} array contains an invalid pattern 388 * or if a {@code null} appears in the array of compact 389 * patterns 390 * @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols) 391 * @see DecimalFormatSymbols 392 */ 393 public CompactNumberFormat(String decimalPattern, 394 DecimalFormatSymbols symbols, String[] compactPatterns) { 395 this(decimalPattern, symbols, compactPatterns, ""); 396 } 397 398 /** 399 * Creates a {@code CompactNumberFormat} using the given decimal pattern, 400 * decimal format symbols, compact patterns, and plural rules. 401 * To obtain the instance of {@code CompactNumberFormat} with the standard 402 * compact patterns for a {@code Locale}, {@code Style}, and {@code pluralRules}, 403 * it is recommended to use the factory methods given by 404 * {@code NumberFormat} for compact number formatting. For example, 405 * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. 406 * 407 * @param decimalPattern a decimal pattern for general number formatting 408 * @param symbols the set of symbols to be used 409 * @param compactPatterns an array of 410 * <a href = "CompactNumberFormat.html#compact_number_patterns"> 411 * compact number patterns</a> 412 * @param pluralRules a String designating plural rules which associate 413 * the {@code Count} keyword, such as "{@code one}", and the 414 * actual integer number. Its syntax is defined in Unicode Consortium's 415 * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax"> 416 * Plural rules syntax</a> 417 * @throws NullPointerException if any of the given arguments is 418 * {@code null} 419 * @throws IllegalArgumentException if the given {@code decimalPattern}, 420 * the {@code compactPatterns} array contains an invalid pattern, 421 * a {@code null} appears in the array of compact patterns, 422 * or if the given {@code pluralRules} contains an invalid syntax 423 * @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols) 424 * @see DecimalFormatSymbols 425 * @since 14 426 */ 427 public CompactNumberFormat(String decimalPattern, 428 DecimalFormatSymbols symbols, String[] compactPatterns, 429 String pluralRules) { 430 431 Objects.requireNonNull(decimalPattern, "decimalPattern"); 432 Objects.requireNonNull(symbols, "symbols"); 433 Objects.requireNonNull(compactPatterns, "compactPatterns"); 434 Objects.requireNonNull(pluralRules, "pluralRules"); 435 436 this.symbols = symbols; 437 // Instantiating the DecimalFormat with "0" pattern; this acts just as a 438 // basic pattern; the properties (For example, prefix/suffix) 439 // are later computed based on the compact number formatting process. 440 decimalFormat = new DecimalFormat(SPECIAL_PATTERN, this.symbols); 441 442 // Initializing the super class state with the decimalFormat values 443 // to represent this CompactNumberFormat. 444 // For setting the digits counts, use overridden setXXX methods of this 445 // CompactNumberFormat, as it performs check with the max range allowed 446 // for compact number formatting 447 setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 448 setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 449 setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); 450 setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); 451 452 super.setGroupingUsed(decimalFormat.isGroupingUsed()); 453 super.setParseIntegerOnly(decimalFormat.isParseIntegerOnly()); 454 455 this.compactPatterns = compactPatterns; 456 457 // DecimalFormat used for formatting numbers with special pattern "0". 458 // Formatting is delegated to the DecimalFormat's number formatting 459 // with no fraction digits 460 this.decimalPattern = decimalPattern; 461 defaultDecimalFormat = new DecimalFormat(this.decimalPattern, 462 this.symbols); 463 defaultDecimalFormat.setMaximumFractionDigits(0); 464 465 this.pluralRules = pluralRules; 466 467 // Process compact patterns to extract the prefixes, suffixes, place holders, and 468 // divisors 469 processCompactPatterns(); 470 } 471 472 /** 473 * Formats a number to produce a string representing its compact form. 474 * The number can be of any subclass of {@link java.lang.Number}. 475 * @param number the number to format 476 * @param toAppendTo the {@code StringBuffer} to which the formatted 477 * text is to be appended 478 * @param fieldPosition keeps track on the position of the field within 479 * the returned string. For example, for formatting 480 * a number {@code 123456789} in the 481 * {@link java.util.Locale#US US locale}, 482 * if the given {@code fieldPosition} is 483 * {@link NumberFormat#INTEGER_FIELD}, the begin 484 * index and end index of {@code fieldPosition} 485 * will be set to 0 and 3, respectively for the 486 * output string {@code 123M}. Similarly, positions 487 * of the prefix and the suffix fields can be 488 * obtained using {@link NumberFormat.Field#PREFIX} 489 * and {@link NumberFormat.Field#SUFFIX} respectively. 490 * @return the {@code StringBuffer} passed in as {@code toAppendTo} 491 * @throws IllegalArgumentException if {@code number} is 492 * {@code null} or not an instance of {@code Number} 493 * @throws NullPointerException if {@code toAppendTo} or 494 * {@code fieldPosition} is {@code null} 495 * @throws ArithmeticException if rounding is needed with rounding 496 * mode being set to {@code RoundingMode.UNNECESSARY} 497 * @see FieldPosition 498 */ 499 @Override 500 public final StringBuffer format(Object number, 501 StringBuffer toAppendTo, 502 FieldPosition fieldPosition) { 503 504 if (number == null) { 505 throw new IllegalArgumentException("Cannot format null as a number"); 506 } 507 508 if (number instanceof Long || number instanceof Integer 509 || number instanceof Short || number instanceof Byte 510 || number instanceof AtomicInteger 511 || number instanceof AtomicLong 512 || (number instanceof BigInteger 513 && ((BigInteger) number).bitLength() < 64)) { 514 return format(((Number) number).longValue(), toAppendTo, 515 fieldPosition); 516 } else if (number instanceof BigDecimal) { 517 return format((BigDecimal) number, toAppendTo, fieldPosition); 518 } else if (number instanceof BigInteger) { 519 return format((BigInteger) number, toAppendTo, fieldPosition); 520 } else if (number instanceof Number) { 521 return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); 522 } else { 523 throw new IllegalArgumentException("Cannot format " 524 + number.getClass().getName() + " as a number"); 525 } 526 } 527 528 /** 529 * Formats a double to produce a string representing its compact form. 530 * @param number the double number to format 531 * @param result where the text is to be appended 532 * @param fieldPosition keeps track on the position of the field within 533 * the returned string. For example, to format 534 * a number {@code 1234567.89} in the 535 * {@link java.util.Locale#US US locale} 536 * if the given {@code fieldPosition} is 537 * {@link NumberFormat#INTEGER_FIELD}, the begin 538 * index and end index of {@code fieldPosition} 539 * will be set to 0 and 1, respectively for the 540 * output string {@code 1M}. Similarly, positions 541 * of the prefix and the suffix fields can be 542 * obtained using {@link NumberFormat.Field#PREFIX} 543 * and {@link NumberFormat.Field#SUFFIX} respectively. 544 * @return the {@code StringBuffer} passed in as {@code result} 545 * @throws NullPointerException if {@code result} or 546 * {@code fieldPosition} is {@code null} 547 * @throws ArithmeticException if rounding is needed with rounding 548 * mode being set to {@code RoundingMode.UNNECESSARY} 549 * @see FieldPosition 550 */ 551 @Override 552 public StringBuffer format(double number, StringBuffer result, 553 FieldPosition fieldPosition) { 554 555 fieldPosition.setBeginIndex(0); 556 fieldPosition.setEndIndex(0); 557 return format(number, result, fieldPosition.getFieldDelegate()); 558 } 559 560 private StringBuffer format(double number, StringBuffer result, 561 FieldDelegate delegate) { 562 563 boolean nanOrInfinity = decimalFormat.handleNaN(number, result, delegate); 564 if (nanOrInfinity) { 565 return result; 566 } 567 568 boolean isNegative = ((number < 0.0) 569 || (number == 0.0 && 1 / number < 0.0)); 570 571 nanOrInfinity = decimalFormat.handleInfinity(number, result, delegate, isNegative); 572 if (nanOrInfinity) { 573 return result; 574 } 575 576 // Round the double value with min fraction digits, the integer 577 // part of the rounded value is used for matching the compact 578 // number pattern 579 // For example, if roundingMode is HALF_UP with min fraction 580 // digits = 0, the number 999.6 should round up 581 // to 1000 and outputs 1K/thousand in "en_US" locale 582 DigitList dList = new DigitList(); 583 dList.setRoundingMode(getRoundingMode()); 584 number = isNegative ? -number : number; 585 dList.set(isNegative, number, getMinimumFractionDigits()); 586 587 double roundedNumber = dList.getDouble(); 588 int compactDataIndex = selectCompactPattern((long) roundedNumber); 589 if (compactDataIndex != -1) { 590 long divisor = (Long) divisors.get(compactDataIndex); 591 int iPart = getIntegerPart(number, divisor); 592 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 593 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 594 595 if (!prefix.isEmpty() || !suffix.isEmpty()) { 596 appendPrefix(result, prefix, delegate); 597 if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) { 598 roundedNumber = roundedNumber / divisor; 599 decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits()); 600 decimalFormat.subformatNumber(result, delegate, isNegative, 601 false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), 602 getMaximumFractionDigits(), getMinimumFractionDigits()); 603 appendSuffix(result, suffix, delegate); 604 } 605 } else { 606 defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative); 607 } 608 } else { 609 defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative); 610 } 611 return result; 612 } 613 614 /** 615 * Formats a long to produce a string representing its compact form. 616 * @param number the long number to format 617 * @param result where the text is to be appended 618 * @param fieldPosition keeps track on the position of the field within 619 * the returned string. For example, to format 620 * a number {@code 123456789} in the 621 * {@link java.util.Locale#US US locale}, 622 * if the given {@code fieldPosition} is 623 * {@link NumberFormat#INTEGER_FIELD}, the begin 624 * index and end index of {@code fieldPosition} 625 * will be set to 0 and 3, respectively for the 626 * output string {@code 123M}. Similarly, positions 627 * of the prefix and the suffix fields can be 628 * obtained using {@link NumberFormat.Field#PREFIX} 629 * and {@link NumberFormat.Field#SUFFIX} respectively. 630 * @return the {@code StringBuffer} passed in as {@code result} 631 * @throws NullPointerException if {@code result} or 632 * {@code fieldPosition} is {@code null} 633 * @throws ArithmeticException if rounding is needed with rounding 634 * mode being set to {@code RoundingMode.UNNECESSARY} 635 * @see FieldPosition 636 */ 637 @Override 638 public StringBuffer format(long number, StringBuffer result, 639 FieldPosition fieldPosition) { 640 641 fieldPosition.setBeginIndex(0); 642 fieldPosition.setEndIndex(0); 643 return format(number, result, fieldPosition.getFieldDelegate()); 644 } 645 646 private StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) { 647 boolean isNegative = (number < 0); 648 if (isNegative) { 649 number = -number; 650 } 651 652 if (number < 0) { // LONG_MIN 653 BigInteger bigIntegerValue = BigInteger.valueOf(number); 654 return format(bigIntegerValue, result, delegate, true); 655 } 656 657 int compactDataIndex = selectCompactPattern(number); 658 if (compactDataIndex != -1) { 659 long divisor = (Long) divisors.get(compactDataIndex); 660 int iPart = getIntegerPart(number, divisor); 661 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 662 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 663 if (!prefix.isEmpty() || !suffix.isEmpty()) { 664 appendPrefix(result, prefix, delegate); 665 if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) { 666 if ((number % divisor == 0)) { 667 number = number / divisor; 668 decimalFormat.setDigitList(number, isNegative, 0); 669 decimalFormat.subformatNumber(result, delegate, 670 isNegative, true, getMaximumIntegerDigits(), 671 getMinimumIntegerDigits(), getMaximumFractionDigits(), 672 getMinimumFractionDigits()); 673 } else { 674 // To avoid truncation of fractional part store 675 // the value in double and follow double path instead of 676 // long path 677 double dNumber = (double) number / divisor; 678 decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits()); 679 decimalFormat.subformatNumber(result, delegate, 680 isNegative, false, getMaximumIntegerDigits(), 681 getMinimumIntegerDigits(), getMaximumFractionDigits(), 682 getMinimumFractionDigits()); 683 } 684 appendSuffix(result, suffix, delegate); 685 } 686 } else { 687 number = isNegative ? -number : number; 688 defaultDecimalFormat.format(number, result, delegate); 689 } 690 } else { 691 number = isNegative ? -number : number; 692 defaultDecimalFormat.format(number, result, delegate); 693 } 694 return result; 695 } 696 697 /** 698 * Formats a BigDecimal to produce a string representing its compact form. 699 * @param number the BigDecimal number to format 700 * @param result where the text is to be appended 701 * @param fieldPosition keeps track on the position of the field within 702 * the returned string. For example, to format 703 * a number {@code 1234567.89} in the 704 * {@link java.util.Locale#US US locale}, 705 * if the given {@code fieldPosition} is 706 * {@link NumberFormat#INTEGER_FIELD}, the begin 707 * index and end index of {@code fieldPosition} 708 * will be set to 0 and 1, respectively for the 709 * output string {@code 1M}. Similarly, positions 710 * of the prefix and the suffix fields can be 711 * obtained using {@link NumberFormat.Field#PREFIX} 712 * and {@link NumberFormat.Field#SUFFIX} respectively. 713 * @return the {@code StringBuffer} passed in as {@code result} 714 * @throws ArithmeticException if rounding is needed with rounding 715 * mode being set to {@code RoundingMode.UNNECESSARY} 716 * @throws NullPointerException if any of the given parameter 717 * is {@code null} 718 * @see FieldPosition 719 */ 720 private StringBuffer format(BigDecimal number, StringBuffer result, 721 FieldPosition fieldPosition) { 722 723 Objects.requireNonNull(number); 724 fieldPosition.setBeginIndex(0); 725 fieldPosition.setEndIndex(0); 726 return format(number, result, fieldPosition.getFieldDelegate()); 727 } 728 729 private StringBuffer format(BigDecimal number, StringBuffer result, 730 FieldDelegate delegate) { 731 732 boolean isNegative = number.signum() == -1; 733 if (isNegative) { 734 number = number.negate(); 735 } 736 737 // Round the value with min fraction digits, the integer 738 // part of the rounded value is used for matching the compact 739 // number pattern 740 // For example, If roundingMode is HALF_UP with min fraction digits = 0, 741 // the number 999.6 should round up 742 // to 1000 and outputs 1K/thousand in "en_US" locale 743 number = number.setScale(getMinimumFractionDigits(), getRoundingMode()); 744 745 int compactDataIndex; 746 if (number.toBigInteger().bitLength() < 64) { 747 long longNumber = number.toBigInteger().longValue(); 748 compactDataIndex = selectCompactPattern(longNumber); 749 } else { 750 compactDataIndex = selectCompactPattern(number.toBigInteger()); 751 } 752 753 if (compactDataIndex != -1) { 754 Number divisor = divisors.get(compactDataIndex); 755 int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); 756 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 757 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 758 if (!prefix.isEmpty() || !suffix.isEmpty()) { 759 appendPrefix(result, prefix, delegate); 760 if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) { 761 number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode()); 762 decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits()); 763 decimalFormat.subformatNumber(result, delegate, isNegative, 764 false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), 765 getMaximumFractionDigits(), getMinimumFractionDigits()); 766 appendSuffix(result, suffix, delegate); 767 } 768 } else { 769 number = isNegative ? number.negate() : number; 770 defaultDecimalFormat.format(number, result, delegate); 771 } 772 } else { 773 number = isNegative ? number.negate() : number; 774 defaultDecimalFormat.format(number, result, delegate); 775 } 776 return result; 777 } 778 779 /** 780 * Formats a BigInteger to produce a string representing its compact form. 781 * @param number the BigInteger number to format 782 * @param result where the text is to be appended 783 * @param fieldPosition keeps track on the position of the field within 784 * the returned string. For example, to format 785 * a number {@code 123456789} in the 786 * {@link java.util.Locale#US US locale}, 787 * if the given {@code fieldPosition} is 788 * {@link NumberFormat#INTEGER_FIELD}, the begin index 789 * and end index of {@code fieldPosition} will be set 790 * to 0 and 3, respectively for the output string 791 * {@code 123M}. Similarly, positions of the 792 * prefix and the suffix fields can be obtained 793 * using {@link NumberFormat.Field#PREFIX} and 794 * {@link NumberFormat.Field#SUFFIX} respectively. 795 * @return the {@code StringBuffer} passed in as {@code result} 796 * @throws ArithmeticException if rounding is needed with rounding 797 * mode being set to {@code RoundingMode.UNNECESSARY} 798 * @throws NullPointerException if any of the given parameter 799 * is {@code null} 800 * @see FieldPosition 801 */ 802 private StringBuffer format(BigInteger number, StringBuffer result, 803 FieldPosition fieldPosition) { 804 805 Objects.requireNonNull(number); 806 fieldPosition.setBeginIndex(0); 807 fieldPosition.setEndIndex(0); 808 return format(number, result, fieldPosition.getFieldDelegate(), false); 809 } 810 811 private StringBuffer format(BigInteger number, StringBuffer result, 812 FieldDelegate delegate, boolean formatLong) { 813 814 boolean isNegative = number.signum() == -1; 815 if (isNegative) { 816 number = number.negate(); 817 } 818 819 int compactDataIndex = selectCompactPattern(number); 820 if (compactDataIndex != -1) { 821 Number divisor = divisors.get(compactDataIndex); 822 int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); 823 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 824 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 825 if (!prefix.isEmpty() || !suffix.isEmpty()) { 826 appendPrefix(result, prefix, delegate); 827 if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) { 828 if (number.mod(new BigInteger(divisor.toString())) 829 .compareTo(BigInteger.ZERO) == 0) { 830 number = number.divide(new BigInteger(divisor.toString())); 831 832 decimalFormat.setDigitList(number, isNegative, 0); 833 decimalFormat.subformatNumber(result, delegate, 834 isNegative, true, getMaximumIntegerDigits(), 835 getMinimumIntegerDigits(), getMaximumFractionDigits(), 836 getMinimumFractionDigits()); 837 } else { 838 // To avoid truncation of fractional part store the value in 839 // BigDecimal and follow BigDecimal path instead of 840 // BigInteger path 841 BigDecimal nDecimal = new BigDecimal(number) 842 .divide(new BigDecimal(divisor.toString()), getRoundingMode()); 843 decimalFormat.setDigitList(nDecimal, isNegative, getMaximumFractionDigits()); 844 decimalFormat.subformatNumber(result, delegate, 845 isNegative, false, getMaximumIntegerDigits(), 846 getMinimumIntegerDigits(), getMaximumFractionDigits(), 847 getMinimumFractionDigits()); 848 } 849 appendSuffix(result, suffix, delegate); 850 } 851 } else { 852 number = isNegative ? number.negate() : number; 853 defaultDecimalFormat.format(number, result, delegate, formatLong); 854 } 855 } else { 856 number = isNegative ? number.negate() : number; 857 defaultDecimalFormat.format(number, result, delegate, formatLong); 858 } 859 return result; 860 } 861 862 /** 863 * Obtain the designated affix from the appropriate list of affixes, 864 * based on the given arguments. 865 */ 866 private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative, int compactDataIndex, int iPart) { 867 return (isExpanded ? (isPrefix ? (isNegative ? negativePrefixes : positivePrefixes) : 868 (isNegative ? negativeSuffixes : positiveSuffixes)) : 869 (isPrefix ? (isNegative ? negativePrefixPatterns : positivePrefixPatterns) : 870 (isNegative ? negativeSuffixPatterns : positiveSuffixPatterns))) 871 .get(compactDataIndex).get(iPart); 872 } 873 874 /** 875 * Appends the {@code prefix} to the {@code result} and also set the 876 * {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.PREFIX} 877 * field positions. 878 * @param result the resulting string, where the pefix is to be appended 879 * @param prefix prefix to append 880 * @param delegate notified of the locations of 881 * {@code NumberFormat.Field.SIGN} and 882 * {@code NumberFormat.Field.PREFIX} fields 883 */ 884 private void appendPrefix(StringBuffer result, String prefix, 885 FieldDelegate delegate) { 886 append(result, expandAffix(prefix), delegate, 887 getFieldPositions(prefix, NumberFormat.Field.PREFIX)); 888 } 889 890 /** 891 * Appends {@code suffix} to the {@code result} and also set the 892 * {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.SUFFIX} 893 * field positions. 894 * @param result the resulting string, where the suffix is to be appended 895 * @param suffix suffix to append 896 * @param delegate notified of the locations of 897 * {@code NumberFormat.Field.SIGN} and 898 * {@code NumberFormat.Field.SUFFIX} fields 899 */ 900 private void appendSuffix(StringBuffer result, String suffix, 901 FieldDelegate delegate) { 902 append(result, expandAffix(suffix), delegate, 903 getFieldPositions(suffix, NumberFormat.Field.SUFFIX)); 904 } 905 906 /** 907 * Appends the {@code string} to the {@code result}. 908 * {@code delegate} is notified of SIGN, PREFIX and/or SUFFIX 909 * field positions. 910 * @param result the resulting string, where the text is to be appended 911 * @param string the text to append 912 * @param delegate notified of the locations of sub fields 913 * @param positions a list of {@code FieldPostion} in the given 914 * string 915 */ 916 private void append(StringBuffer result, String string, 917 FieldDelegate delegate, List<FieldPosition> positions) { 918 if (!string.isEmpty()) { 919 int start = result.length(); 920 result.append(string); 921 for (FieldPosition fp : positions) { 922 Format.Field attribute = fp.getFieldAttribute(); 923 delegate.formatted(attribute, attribute, 924 start + fp.getBeginIndex(), 925 start + fp.getEndIndex(), result); 926 } 927 } 928 } 929 930 /** 931 * Expands an affix {@code pattern} into a string of literals. 932 * All characters in the pattern are literals unless prefixed by QUOTE. 933 * The character prefixed by QUOTE is replaced with its respective 934 * localized literal. 935 * @param pattern a compact number pattern affix 936 * @return an expanded affix 937 */ 938 private String expandAffix(String pattern) { 939 // Return if no quoted character exists 940 if (pattern.indexOf(QUOTE) < 0) { 941 return pattern; 942 } 943 StringBuilder sb = new StringBuilder(); 944 for (int index = 0; index < pattern.length();) { 945 char ch = pattern.charAt(index++); 946 if (ch == QUOTE) { 947 ch = pattern.charAt(index++); 948 if (ch == MINUS_SIGN) { 949 sb.append(symbols.getMinusSignText()); 950 continue; 951 } 952 } 953 sb.append(ch); 954 } 955 return sb.toString(); 956 } 957 958 /** 959 * Returns a list of {@code FieldPostion} in the given {@code pattern}. 960 * @param pattern the pattern to be parsed for {@code FieldPosition} 961 * @param field whether a PREFIX or SUFFIX field 962 * @return a list of {@code FieldPostion} 963 */ 964 private List<FieldPosition> getFieldPositions(String pattern, Field field) { 965 List<FieldPosition> positions = new ArrayList<>(); 966 StringBuilder affix = new StringBuilder(); 967 int stringIndex = 0; 968 for (int index = 0; index < pattern.length();) { 969 char ch = pattern.charAt(index++); 970 if (ch == QUOTE) { 971 ch = pattern.charAt(index++); 972 if (ch == MINUS_SIGN) { 973 String minusText = symbols.getMinusSignText(); 974 FieldPosition fp = new FieldPosition(NumberFormat.Field.SIGN); 975 fp.setBeginIndex(stringIndex); 976 fp.setEndIndex(stringIndex + minusText.length()); 977 positions.add(fp); 978 stringIndex += minusText.length(); 979 affix.append(minusText); 980 continue; 981 } 982 } 983 stringIndex++; 984 affix.append(ch); 985 } 986 if (affix.length() != 0) { 987 FieldPosition fp = new FieldPosition(field); 988 fp.setBeginIndex(0); 989 fp.setEndIndex(affix.length()); 990 positions.add(fp); 991 } 992 return positions; 993 } 994 995 /** 996 * Select the index of the matched compact number pattern for 997 * the given {@code long} {@code number}. 998 * 999 * @param number number to be formatted 1000 * @return index of matched compact pattern; 1001 * -1 if no compact patterns specified 1002 */ 1003 private int selectCompactPattern(long number) { 1004 1005 if (compactPatterns.length == 0) { 1006 return -1; 1007 } 1008 1009 // Minimum index can be "0", max index can be "size - 1" 1010 int dataIndex = number <= 1 ? 0 : (int) Math.log10(number); 1011 dataIndex = Math.min(dataIndex, compactPatterns.length - 1); 1012 return dataIndex; 1013 } 1014 1015 /** 1016 * Select the index of the matched compact number 1017 * pattern for the given {@code BigInteger} {@code number}. 1018 * 1019 * @param number number to be formatted 1020 * @return index of matched compact pattern; 1021 * -1 if no compact patterns specified 1022 */ 1023 private int selectCompactPattern(BigInteger number) { 1024 1025 int matchedIndex = -1; 1026 if (compactPatterns.length == 0) { 1027 return matchedIndex; 1028 } 1029 1030 BigInteger currentValue = BigInteger.ONE; 1031 1032 // For formatting a number, the greatest type less than 1033 // or equal to number is used 1034 for (int index = 0; index < compactPatterns.length; index++) { 1035 if (number.compareTo(currentValue) > 0) { 1036 // Input number is greater than current type; try matching with 1037 // the next 1038 matchedIndex = index; 1039 currentValue = currentValue.multiply(BigInteger.valueOf(RANGE_MULTIPLIER)); 1040 continue; 1041 } 1042 if (number.compareTo(currentValue) < 0) { 1043 // Current type is greater than the input number; 1044 // take the previous pattern 1045 break; 1046 } else { 1047 // Equal 1048 matchedIndex = index; 1049 break; 1050 } 1051 } 1052 return matchedIndex; 1053 } 1054 1055 /** 1056 * Formats an Object producing an {@code AttributedCharacterIterator}. 1057 * The returned {@code AttributedCharacterIterator} can be used 1058 * to build the resulting string, as well as to determine information 1059 * about the resulting string. 1060 * <p> 1061 * Each attribute key of the {@code AttributedCharacterIterator} will 1062 * be of type {@code NumberFormat.Field}, with the attribute value 1063 * being the same as the attribute key. The prefix and the suffix 1064 * parts of the returned iterator (if present) are represented by 1065 * the attributes {@link NumberFormat.Field#PREFIX} and 1066 * {@link NumberFormat.Field#SUFFIX} respectively. 1067 * 1068 * 1069 * @throws NullPointerException if obj is null 1070 * @throws IllegalArgumentException when the Format cannot format the 1071 * given object 1072 * @throws ArithmeticException if rounding is needed with rounding 1073 * mode being set to {@code RoundingMode.UNNECESSARY} 1074 * @param obj The object to format 1075 * @return an {@code AttributedCharacterIterator} describing the 1076 * formatted value 1077 */ 1078 @Override 1079 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { 1080 CharacterIteratorFieldDelegate delegate 1081 = new CharacterIteratorFieldDelegate(); 1082 StringBuffer sb = new StringBuffer(); 1083 1084 if (obj instanceof Double || obj instanceof Float) { 1085 format(((Number) obj).doubleValue(), sb, delegate); 1086 } else if (obj instanceof Long || obj instanceof Integer 1087 || obj instanceof Short || obj instanceof Byte 1088 || obj instanceof AtomicInteger || obj instanceof AtomicLong) { 1089 format(((Number) obj).longValue(), sb, delegate); 1090 } else if (obj instanceof BigDecimal) { 1091 format((BigDecimal) obj, sb, delegate); 1092 } else if (obj instanceof BigInteger) { 1093 format((BigInteger) obj, sb, delegate, false); 1094 } else if (obj == null) { 1095 throw new NullPointerException( 1096 "formatToCharacterIterator must be passed non-null object"); 1097 } else { 1098 throw new IllegalArgumentException( 1099 "Cannot format given Object as a Number"); 1100 } 1101 return delegate.getIterator(sb.toString()); 1102 } 1103 1104 /** 1105 * Computes the divisor using minimum integer digits and 1106 * matched pattern index. 1107 * @param minIntDigits string of 0s in compact pattern 1108 * @param patternIndex index of matched compact pattern 1109 * @return divisor value for the number matching the compact 1110 * pattern at given {@code patternIndex} 1111 */ 1112 private Number computeDivisor(String minIntDigits, int patternIndex) { 1113 int count = minIntDigits.length(); 1114 Number matchedValue; 1115 // The divisor value can go above long range, if the compact patterns 1116 // goes above index 18, divisor may need to be stored as BigInteger, 1117 // since long can't store numbers >= 10^19, 1118 if (patternIndex < 19) { 1119 matchedValue = (long) Math.pow(RANGE_MULTIPLIER, patternIndex); 1120 } else { 1121 matchedValue = BigInteger.valueOf(RANGE_MULTIPLIER).pow(patternIndex); 1122 } 1123 Number divisor = matchedValue; 1124 if (count > 0) { 1125 if (matchedValue instanceof BigInteger) { 1126 BigInteger bigValue = (BigInteger) matchedValue; 1127 if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1))) < 0) { 1128 throw new IllegalArgumentException("Invalid Pattern" 1129 + " [" + compactPatterns[patternIndex] 1130 + "]: min integer digits specified exceeds the limit" 1131 + " for the index " + patternIndex); 1132 } 1133 divisor = bigValue.divide(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1))); 1134 } else { 1135 long longValue = (long) matchedValue; 1136 if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count - 1)) { 1137 throw new IllegalArgumentException("Invalid Pattern" 1138 + " [" + compactPatterns[patternIndex] 1139 + "]: min integer digits specified exceeds the limit" 1140 + " for the index " + patternIndex); 1141 } 1142 divisor = longValue / (long) Math.pow(RANGE_MULTIPLIER, count - 1); 1143 } 1144 } 1145 return divisor; 1146 } 1147 1148 /** 1149 * Process the series of compact patterns to compute the 1150 * series of prefixes, suffixes and their respective divisor 1151 * value. 1152 * 1153 */ 1154 private static final Pattern PLURALS = 1155 Pattern.compile("^\\{(?<plurals>.*)}$"); 1156 private static final Pattern COUNT_PATTERN = 1157 Pattern.compile("(zero|one|two|few|many|other):((' '|[^ ])+)[ ]*"); 1158 private void processCompactPatterns() { 1159 int size = compactPatterns.length; 1160 positivePrefixPatterns = new ArrayList<>(size); 1161 negativePrefixPatterns = new ArrayList<>(size); 1162 positiveSuffixPatterns = new ArrayList<>(size); 1163 negativeSuffixPatterns = new ArrayList<>(size); 1164 divisors = new ArrayList<>(size); 1165 placeHolders = new ArrayList<>(size); 1166 1167 for (int index = 0; index < size; index++) { 1168 String text = compactPatterns[index]; 1169 positivePrefixPatterns.add(new Patterns()); 1170 negativePrefixPatterns.add(new Patterns()); 1171 positiveSuffixPatterns.add(new Patterns()); 1172 negativeSuffixPatterns.add(new Patterns()); 1173 placeHolders.add(new Patterns()); 1174 1175 // check if it is the old style 1176 Matcher m = text != null ? PLURALS.matcher(text) : null; 1177 if (m != null && m.matches()) { 1178 final int idx = index; 1179 String plurals = m.group("plurals"); 1180 COUNT_PATTERN.matcher(plurals).results() 1181 .forEach(mr -> applyPattern(mr.group(1), mr.group(2), idx)); 1182 } else { 1183 applyPattern("other", text, index); 1184 } 1185 } 1186 1187 rulesMap = buildPluralRulesMap(); 1188 } 1189 1190 /** 1191 * Build the plural rules map. 1192 * 1193 * @throws IllegalArgumentException if the {@code pluralRules} has invalid syntax, 1194 * or its length exceeds 2,048 chars 1195 */ 1196 private Map<String, String> buildPluralRulesMap() { 1197 // length limitation check. 2K for now. 1198 if (pluralRules.length() > 2_048) { 1199 throw new IllegalArgumentException("plural rules is too long (> 2,048)"); 1200 } 1201 1202 try { 1203 return Arrays.stream(pluralRules.split(";")) 1204 .map(this::validateRule) 1205 .collect(Collectors.toMap( 1206 r -> r.replaceFirst(":.*", ""), 1207 r -> r.replaceFirst("[^:]+:", "") 1208 )); 1209 } catch (IllegalStateException ise) { 1210 throw new IllegalArgumentException(ise); 1211 } 1212 } 1213 1214 // Patterns for plurals syntax validation 1215 private final static String EXPR = "([niftvw])\\s*(([/%])\\s*(\\d+))*"; 1216 private final static String RELATION = "(!?=)"; 1217 private final static String VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)"; 1218 private final static String CONDITION = EXPR + "\\s*" + 1219 RELATION + "\\s*" + 1220 VALUE_RANGE + "\\s*" + 1221 "(,\\s*" + VALUE_RANGE + ")*"; 1222 private final static Pattern PLURALRULES_PATTERN = 1223 Pattern.compile("(zero|one|two|few|many):\\s*" + 1224 CONDITION + 1225 "(\\s*(and|or)\\s*" + CONDITION + ")*"); 1226 1227 /** 1228 * Validates a plural rule. 1229 * @param rule rule to validate 1230 * @throws IllegalArgumentException if the {@code rule} has invalid syntax 1231 * @return the input rule (trimmed) 1232 */ 1233 private String validateRule(String rule) { 1234 rule = rule.trim(); 1235 if (!rule.isEmpty() && !rule.equals("other:")) { 1236 Matcher validator = PLURALRULES_PATTERN.matcher(rule); 1237 if (!validator.matches()) { 1238 throw new IllegalArgumentException("Invalid plural rules syntax: " + rule); 1239 } 1240 } 1241 1242 return rule; 1243 } 1244 1245 /** 1246 * Process a compact pattern at a specific {@code index} 1247 * @param pattern the compact pattern to be processed 1248 * @param index index in the array of compact patterns 1249 * 1250 */ 1251 private void applyPattern(String count, String pattern, int index) { 1252 1253 if (pattern == null) { 1254 throw new IllegalArgumentException("A null compact pattern" + 1255 " encountered at index: " + index); 1256 } 1257 1258 int start = 0; 1259 boolean gotNegative = false; 1260 1261 String positivePrefix = ""; 1262 String positiveSuffix = ""; 1263 String negativePrefix = ""; 1264 String negativeSuffix = ""; 1265 String zeros = ""; 1266 for (int j = 1; j >= 0 && start < pattern.length(); --j) { 1267 1268 StringBuffer prefix = new StringBuffer(); 1269 StringBuffer suffix = new StringBuffer(); 1270 boolean inQuote = false; 1271 // The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is 1272 // the section of the pattern with digits. Phase 2 is the suffix. 1273 // The separation of the characters into phases is 1274 // strictly enforced; if phase 1 characters are to appear in the 1275 // suffix, for example, they must be quoted. 1276 int phase = 0; 1277 1278 // The affix is either the prefix or the suffix. 1279 StringBuffer affix = prefix; 1280 1281 for (int pos = start; pos < pattern.length(); ++pos) { 1282 char ch = pattern.charAt(pos); 1283 switch (phase) { 1284 case 0: 1285 case 2: 1286 // Process the prefix / suffix characters 1287 if (inQuote) { 1288 // A quote within quotes indicates either the closing 1289 // quote or two quotes, which is a quote literal. That 1290 // is, we have the second quote in 'do' or 'don''t'. 1291 if (ch == QUOTE) { 1292 if ((pos + 1) < pattern.length() 1293 && pattern.charAt(pos + 1) == QUOTE) { 1294 ++pos; 1295 affix.append("''"); // 'don''t' 1296 } else { 1297 inQuote = false; // 'do' 1298 } 1299 continue; 1300 } 1301 } else { 1302 // Process unquoted characters seen in prefix or suffix 1303 // phase. 1304 switch (ch) { 1305 case ZERO_DIGIT: 1306 phase = 1; 1307 --pos; // Reprocess this character 1308 continue; 1309 case QUOTE: 1310 // A quote outside quotes indicates either the 1311 // opening quote or two quotes, which is a quote 1312 // literal. That is, we have the first quote in 'do' 1313 // or o''clock. 1314 if ((pos + 1) < pattern.length() 1315 && pattern.charAt(pos + 1) == QUOTE) { 1316 ++pos; 1317 affix.append("''"); // o''clock 1318 } else { 1319 inQuote = true; // 'do' 1320 } 1321 continue; 1322 case SEPARATOR: 1323 // Don't allow separators before we see digit 1324 // characters of phase 1, and don't allow separators 1325 // in the second pattern (j == 0). 1326 if (phase == 0 || j == 0) { 1327 throw new IllegalArgumentException( 1328 "Unquoted special character '" 1329 + ch + "' in pattern \"" + pattern + "\""); 1330 } 1331 start = pos + 1; 1332 pos = pattern.length(); 1333 continue; 1334 case MINUS_SIGN: 1335 affix.append("'-"); 1336 continue; 1337 case DECIMAL_SEPARATOR: 1338 case GROUPING_SEPARATOR: 1339 case DIGIT: 1340 case PERCENT: 1341 case PER_MILLE: 1342 case CURRENCY_SIGN: 1343 throw new IllegalArgumentException( 1344 "Unquoted special character '" + ch 1345 + "' in pattern \"" + pattern + "\""); 1346 default: 1347 break; 1348 } 1349 } 1350 // Note that if we are within quotes, or if this is an 1351 // unquoted, non-special character, then we usually fall 1352 // through to here. 1353 affix.append(ch); 1354 break; 1355 1356 case 1: 1357 // The negative subpattern (j = 0) serves only to specify the 1358 // negative prefix and suffix, so all the phase 1 characters, 1359 // for example, digits, zeroDigit, groupingSeparator, 1360 // decimalSeparator, exponent are ignored 1361 if (j == 0) { 1362 while (pos < pattern.length()) { 1363 char negPatternChar = pattern.charAt(pos); 1364 if (negPatternChar == ZERO_DIGIT) { 1365 ++pos; 1366 } else { 1367 // Not a phase 1 character, consider it as 1368 // suffix and parse it in phase 2 1369 --pos; //process it again in outer loop 1370 phase = 2; 1371 affix = suffix; 1372 break; 1373 } 1374 } 1375 continue; 1376 } 1377 // Consider only '0' as valid pattern char which can appear 1378 // in number part, rest can be either suffix or prefix 1379 if (ch == ZERO_DIGIT) { 1380 zeros = zeros + "0"; 1381 } else { 1382 phase = 2; 1383 affix = suffix; 1384 --pos; 1385 } 1386 break; 1387 } 1388 } 1389 1390 if (inQuote) { 1391 throw new IllegalArgumentException("Invalid single quote" 1392 + " in pattern \"" + pattern + "\""); 1393 } 1394 1395 if (j == 1) { 1396 positivePrefix = prefix.toString(); 1397 positiveSuffix = suffix.toString(); 1398 negativePrefix = positivePrefix; 1399 negativeSuffix = positiveSuffix; 1400 } else { 1401 negativePrefix = prefix.toString(); 1402 negativeSuffix = suffix.toString(); 1403 gotNegative = true; 1404 } 1405 1406 // If there is no negative pattern, or if the negative pattern is 1407 // identical to the positive pattern, then prepend the minus sign to 1408 // the positive pattern to form the negative pattern. 1409 if (!gotNegative 1410 || (negativePrefix.equals(positivePrefix) 1411 && negativeSuffix.equals(positiveSuffix))) { 1412 negativeSuffix = positiveSuffix; 1413 negativePrefix = "'-" + positivePrefix; 1414 } 1415 } 1416 1417 // Only if positive affix exists; else put empty strings 1418 if (!positivePrefix.isEmpty() || !positiveSuffix.isEmpty()) { 1419 positivePrefixPatterns.get(index).put(count, positivePrefix); 1420 negativePrefixPatterns.get(index).put(count, negativePrefix); 1421 positiveSuffixPatterns.get(index).put(count, positiveSuffix); 1422 negativeSuffixPatterns.get(index).put(count, negativeSuffix); 1423 placeHolders.get(index).put(count, zeros); 1424 if (divisors.size() <= index) { 1425 divisors.add(computeDivisor(zeros, index)); 1426 } 1427 } else { 1428 positivePrefixPatterns.get(index).put(count, ""); 1429 negativePrefixPatterns.get(index).put(count, ""); 1430 positiveSuffixPatterns.get(index).put(count, ""); 1431 negativeSuffixPatterns.get(index).put(count, ""); 1432 placeHolders.get(index).put(count, ""); 1433 if (divisors.size() <= index) { 1434 divisors.add(1L); 1435 } 1436 } 1437 } 1438 1439 private final transient DigitList digitList = new DigitList(); 1440 private static final int STATUS_INFINITE = 0; 1441 private static final int STATUS_POSITIVE = 1; 1442 private static final int STATUS_LENGTH = 2; 1443 1444 private static final char ZERO_DIGIT = '0'; 1445 private static final char DIGIT = '#'; 1446 private static final char DECIMAL_SEPARATOR = '.'; 1447 private static final char GROUPING_SEPARATOR = ','; 1448 private static final char MINUS_SIGN = '-'; 1449 private static final char PERCENT = '%'; 1450 private static final char PER_MILLE = '\u2030'; 1451 private static final char SEPARATOR = ';'; 1452 private static final char CURRENCY_SIGN = '\u00A4'; 1453 private static final char QUOTE = '\''; 1454 1455 // Expanded form of positive/negative prefix/suffix, 1456 // the expanded form contains special characters in 1457 // its localized form, which are used for matching 1458 // while parsing a string to number 1459 private transient List<Patterns> positivePrefixes; 1460 private transient List<Patterns> negativePrefixes; 1461 private transient List<Patterns> positiveSuffixes; 1462 private transient List<Patterns> negativeSuffixes; 1463 1464 private void expandAffixPatterns() { 1465 positivePrefixes = new ArrayList<>(compactPatterns.length); 1466 negativePrefixes = new ArrayList<>(compactPatterns.length); 1467 positiveSuffixes = new ArrayList<>(compactPatterns.length); 1468 negativeSuffixes = new ArrayList<>(compactPatterns.length); 1469 for (int index = 0; index < compactPatterns.length; index++) { 1470 positivePrefixes.add(positivePrefixPatterns.get(index).expandAffix()); 1471 negativePrefixes.add(negativePrefixPatterns.get(index).expandAffix()); 1472 positiveSuffixes.add(positiveSuffixPatterns.get(index).expandAffix()); 1473 negativeSuffixes.add(negativeSuffixPatterns.get(index).expandAffix()); 1474 } 1475 } 1476 1477 /** 1478 * Parses a compact number from a string to produce a {@code Number}. 1479 * <p> 1480 * The method attempts to parse text starting at the index given by 1481 * {@code pos}. 1482 * If parsing succeeds, then the index of {@code pos} is updated 1483 * to the index after the last character used (parsing does not necessarily 1484 * use all characters up to the end of the string), and the parsed 1485 * number is returned. The updated {@code pos} can be used to 1486 * indicate the starting point for the next call to this method. 1487 * If an error occurs, then the index of {@code pos} is not 1488 * changed, the error index of {@code pos} is set to the index of 1489 * the character where the error occurred, and {@code null} is returned. 1490 * <p> 1491 * The value is the numeric part in the given text multiplied 1492 * by the numeric equivalent of the affix attached 1493 * (For example, "K" = 1000 in {@link java.util.Locale#US US locale}). 1494 * The subclass returned depends on the value of 1495 * {@link #isParseBigDecimal}. 1496 * <ul> 1497 * <li>If {@link #isParseBigDecimal()} is false (the default), 1498 * most integer values are returned as {@code Long} 1499 * objects, no matter how they are written: {@code "17K"} and 1500 * {@code "17.000K"} both parse to {@code Long.valueOf(17000)}. 1501 * If the value cannot fit into {@code Long}, then the result is 1502 * returned as {@code Double}. This includes values with a 1503 * fractional part, infinite values, {@code NaN}, 1504 * and the value -0.0. 1505 * <p> 1506 * Callers may use the {@code Number} methods {@code doubleValue}, 1507 * {@code longValue}, etc., to obtain the type they want. 1508 * 1509 * <li>If {@link #isParseBigDecimal()} is true, values are returned 1510 * as {@code BigDecimal} objects. The special cases negative 1511 * and positive infinity and NaN are returned as {@code Double} 1512 * instances holding the values of the corresponding 1513 * {@code Double} constants. 1514 * </ul> 1515 * <p> 1516 * {@code CompactNumberFormat} parses all Unicode characters that represent 1517 * decimal digits, as defined by {@code Character.digit()}. In 1518 * addition, {@code CompactNumberFormat} also recognizes as digits the ten 1519 * consecutive characters starting with the localized zero digit defined in 1520 * the {@code DecimalFormatSymbols} object. 1521 * <p> 1522 * {@code CompactNumberFormat} parse does not allow parsing scientific 1523 * notations. For example, parsing a string {@code "1.05E4K"} in 1524 * {@link java.util.Locale#US US locale} breaks at character 'E' 1525 * and returns 1.05. 1526 * 1527 * @param text the string to be parsed 1528 * @param pos a {@code ParsePosition} object with index and error 1529 * index information as described above 1530 * @return the parsed value, or {@code null} if the parse fails 1531 * @throws NullPointerException if {@code text} or 1532 * {@code pos} is null 1533 * 1534 */ 1535 @Override 1536 public Number parse(String text, ParsePosition pos) { 1537 1538 Objects.requireNonNull(text); 1539 Objects.requireNonNull(pos); 1540 1541 // Lazily expanding the affix patterns, on the first parse 1542 // call on this instance 1543 // If not initialized, expand and load all affixes 1544 if (positivePrefixes == null) { 1545 expandAffixPatterns(); 1546 } 1547 1548 // The compact number multiplier for parsed string. 1549 // Its value is set on parsing prefix and suffix. For example, 1550 // in the {@link java.util.Locale#US US locale} parsing {@code "1K"} 1551 // sets its value to 1000, as K (thousand) is abbreviated form of 1000. 1552 Number cnfMultiplier = 1L; 1553 1554 // Special case NaN 1555 if (text.regionMatches(pos.index, symbols.getNaN(), 1556 0, symbols.getNaN().length())) { 1557 pos.index = pos.index + symbols.getNaN().length(); 1558 return Double.NaN; 1559 } 1560 1561 int position = pos.index; 1562 int oldStart = pos.index; 1563 boolean gotPositive = false; 1564 boolean gotNegative = false; 1565 int matchedPosIndex = -1; 1566 int matchedNegIndex = -1; 1567 String matchedPosPrefix = ""; 1568 String matchedNegPrefix = ""; 1569 String defaultPosPrefix = defaultDecimalFormat.getPositivePrefix(); 1570 String defaultNegPrefix = defaultDecimalFormat.getNegativePrefix(); 1571 double num = parseNumberPart(text, position); 1572 1573 // Prefix matching 1574 for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { 1575 String positivePrefix = getAffix(true, true, false, compactIndex, (int)num); 1576 String negativePrefix = getAffix(true, true, true, compactIndex, (int)num); 1577 1578 // Do not break if a match occur; there is a possibility that the 1579 // subsequent affixes may match the longer subsequence in the given 1580 // string. 1581 // For example, matching "Mdx 3" with "M", "Md" as prefix should 1582 // match with "Md" 1583 boolean match = matchAffix(text, position, positivePrefix, 1584 defaultPosPrefix, matchedPosPrefix); 1585 if (match) { 1586 matchedPosIndex = compactIndex; 1587 matchedPosPrefix = positivePrefix; 1588 gotPositive = true; 1589 } 1590 1591 match = matchAffix(text, position, negativePrefix, 1592 defaultNegPrefix, matchedNegPrefix); 1593 if (match) { 1594 matchedNegIndex = compactIndex; 1595 matchedNegPrefix = negativePrefix; 1596 gotNegative = true; 1597 } 1598 } 1599 1600 // Given text does not match the non empty valid compact prefixes 1601 // check with the default prefixes 1602 if (!gotPositive && !gotNegative) { 1603 if (text.regionMatches(pos.index, defaultPosPrefix, 0, 1604 defaultPosPrefix.length())) { 1605 // Matches the default positive prefix 1606 matchedPosPrefix = defaultPosPrefix; 1607 gotPositive = true; 1608 } 1609 if (text.regionMatches(pos.index, defaultNegPrefix, 0, 1610 defaultNegPrefix.length())) { 1611 // Matches the default negative prefix 1612 matchedNegPrefix = defaultNegPrefix; 1613 gotNegative = true; 1614 } 1615 } 1616 1617 // If both match, take the longest one 1618 if (gotPositive && gotNegative) { 1619 if (matchedPosPrefix.length() > matchedNegPrefix.length()) { 1620 gotNegative = false; 1621 } else if (matchedPosPrefix.length() < matchedNegPrefix.length()) { 1622 gotPositive = false; 1623 } 1624 } 1625 1626 // Update the position and take compact multiplier 1627 // only if it matches the compact prefix, not the default 1628 // prefix; else multiplier should be 1 1629 // If there's no number part, no need to go further, just 1630 // return the multiplier. 1631 if (gotPositive) { 1632 position += matchedPosPrefix.length(); 1633 if (matchedPosIndex != -1) { 1634 cnfMultiplier = divisors.get(matchedPosIndex); 1635 if (placeHolders.get(matchedPosIndex).get(num).isEmpty()) { 1636 pos.index = position; 1637 return cnfMultiplier; 1638 } 1639 } 1640 } else if (gotNegative) { 1641 position += matchedNegPrefix.length(); 1642 if (matchedNegIndex != -1) { 1643 cnfMultiplier = divisors.get(matchedNegIndex); 1644 if (placeHolders.get(matchedNegIndex).get(num).isEmpty()) { 1645 pos.index = position; 1646 return cnfMultiplier; 1647 } 1648 } 1649 } 1650 1651 digitList.setRoundingMode(getRoundingMode()); 1652 boolean[] status = new boolean[STATUS_LENGTH]; 1653 1654 // Call DecimalFormat.subparseNumber() method to parse the 1655 // number part of the input text 1656 position = decimalFormat.subparseNumber(text, position, 1657 digitList, false, false, status); 1658 1659 if (position == -1) { 1660 // Unable to parse the number successfully 1661 pos.index = oldStart; 1662 pos.errorIndex = oldStart; 1663 return null; 1664 } 1665 1666 // If parse integer only is true and the parsing is broken at 1667 // decimal point, then pass/ignore all digits and move pointer 1668 // at the start of suffix, to process the suffix part 1669 if (isParseIntegerOnly() 1670 && text.charAt(position) == symbols.getDecimalSeparator()) { 1671 position++; // Pass decimal character 1672 for (; position < text.length(); ++position) { 1673 char ch = text.charAt(position); 1674 int digit = ch - symbols.getZeroDigit(); 1675 if (digit < 0 || digit > 9) { 1676 digit = Character.digit(ch, 10); 1677 // Parse all digit characters 1678 if (!(digit >= 0 && digit <= 9)) { 1679 break; 1680 } 1681 } 1682 } 1683 } 1684 1685 // Number parsed successfully; match prefix and 1686 // suffix to obtain multiplier 1687 pos.index = position; 1688 Number multiplier = computeParseMultiplier(text, pos, 1689 gotPositive ? matchedPosPrefix : matchedNegPrefix, 1690 status, gotPositive, gotNegative, num); 1691 1692 if (multiplier.longValue() == -1L) { 1693 return null; 1694 } else if (multiplier.longValue() != 1L) { 1695 cnfMultiplier = multiplier; 1696 } 1697 1698 // Special case INFINITY 1699 if (status[STATUS_INFINITE]) { 1700 if (status[STATUS_POSITIVE]) { 1701 return Double.POSITIVE_INFINITY; 1702 } else { 1703 return Double.NEGATIVE_INFINITY; 1704 } 1705 } 1706 1707 if (isParseBigDecimal()) { 1708 BigDecimal bigDecimalResult = digitList.getBigDecimal(); 1709 1710 if (cnfMultiplier.longValue() != 1) { 1711 bigDecimalResult = bigDecimalResult 1712 .multiply(new BigDecimal(cnfMultiplier.toString())); 1713 } 1714 if (!status[STATUS_POSITIVE]) { 1715 bigDecimalResult = bigDecimalResult.negate(); 1716 } 1717 return bigDecimalResult; 1718 } else { 1719 Number cnfResult; 1720 if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { 1721 long longResult = digitList.getLong(); 1722 cnfResult = generateParseResult(longResult, false, 1723 longResult < 0, status, cnfMultiplier); 1724 } else { 1725 cnfResult = generateParseResult(digitList.getDouble(), 1726 true, false, status, cnfMultiplier); 1727 } 1728 return cnfResult; 1729 } 1730 } 1731 1732 private static final Pattern DIGITS = Pattern.compile("\\p{Nd}+"); 1733 /** 1734 * Parse the number part in the input text into a number 1735 * 1736 * @param text input text to be parsed 1737 * @param position starting position 1738 * @return the number 1739 */ 1740 private double parseNumberPart(String text, int position) { 1741 if (text.startsWith(symbols.getInfinity(), position)) { 1742 return Double.POSITIVE_INFINITY; 1743 } else if (!text.startsWith(symbols.getNaN(), position)) { 1744 Matcher m = DIGITS.matcher(text); 1745 if (m.find(position)) { 1746 String digits = m.group(); 1747 int cp = digits.codePointAt(0); 1748 if (Character.isDigit(cp)) { 1749 return Double.parseDouble(digits.codePoints() 1750 .map(Character::getNumericValue) 1751 .mapToObj(Integer::toString) 1752 .collect(Collectors.joining())); 1753 } 1754 } else { 1755 // no numbers. return 1.0 for possible no-placeholder pattern 1756 return 1.0; 1757 } 1758 } 1759 return Double.NaN; 1760 } 1761 1762 /** 1763 * Returns the parsed result by multiplying the parsed number 1764 * with the multiplier representing the prefix and suffix. 1765 * 1766 * @param number parsed number component 1767 * @param gotDouble whether the parsed number contains decimal 1768 * @param gotLongMin whether the parsed number is Long.MIN 1769 * @param status boolean status flags indicating whether the 1770 * value is infinite and whether it is positive 1771 * @param cnfMultiplier compact number multiplier 1772 * @return parsed result 1773 */ 1774 private Number generateParseResult(Number number, boolean gotDouble, 1775 boolean gotLongMin, boolean[] status, Number cnfMultiplier) { 1776 1777 if (gotDouble) { 1778 if (cnfMultiplier.longValue() != 1L) { 1779 double doubleResult = number.doubleValue() * cnfMultiplier.doubleValue(); 1780 doubleResult = (double) convertIfNegative(doubleResult, status, gotLongMin); 1781 // Check if a double can be represeneted as a long 1782 long longResult = (long) doubleResult; 1783 gotDouble = ((doubleResult != (double) longResult) 1784 || (doubleResult == 0.0 && 1 / doubleResult < 0.0)); 1785 return gotDouble ? (Number) doubleResult : (Number) longResult; 1786 } 1787 } else { 1788 if (cnfMultiplier.longValue() != 1L) { 1789 Number result; 1790 if ((cnfMultiplier instanceof Long) && !gotLongMin) { 1791 long longMultiplier = (long) cnfMultiplier; 1792 try { 1793 result = Math.multiplyExact(number.longValue(), 1794 longMultiplier); 1795 } catch (ArithmeticException ex) { 1796 // If number * longMultiplier can not be represented 1797 // as long return as double 1798 result = number.doubleValue() * cnfMultiplier.doubleValue(); 1799 } 1800 } else { 1801 // cnfMultiplier can not be stored into long or the number 1802 // part is Long.MIN, return as double 1803 result = number.doubleValue() * cnfMultiplier.doubleValue(); 1804 } 1805 return convertIfNegative(result, status, gotLongMin); 1806 } 1807 } 1808 1809 // Default number 1810 return convertIfNegative(number, status, gotLongMin); 1811 } 1812 1813 /** 1814 * Negate the parsed value if the positive status flag is false 1815 * and the value is not a Long.MIN 1816 * @param number parsed value 1817 * @param status boolean status flags indicating whether the 1818 * value is infinite and whether it is positive 1819 * @param gotLongMin whether the parsed number is Long.MIN 1820 * @return the resulting value 1821 */ 1822 private Number convertIfNegative(Number number, boolean[] status, 1823 boolean gotLongMin) { 1824 1825 if (!status[STATUS_POSITIVE] && !gotLongMin) { 1826 if (number instanceof Long) { 1827 return -(long) number; 1828 } else { 1829 return -(double) number; 1830 } 1831 } else { 1832 return number; 1833 } 1834 } 1835 1836 /** 1837 * Attempts to match the given {@code affix} in the 1838 * specified {@code text}. 1839 */ 1840 private boolean matchAffix(String text, int position, String affix, 1841 String defaultAffix, String matchedAffix) { 1842 1843 // Check with the compact affixes which are non empty and 1844 // do not match with default affix 1845 if (!affix.isEmpty() && !affix.equals(defaultAffix)) { 1846 // Look ahead only for the longer match than the previous match 1847 if (matchedAffix.length() < affix.length()) { 1848 return text.regionMatches(position, affix, 0, affix.length()); 1849 } 1850 } 1851 return false; 1852 } 1853 1854 /** 1855 * Attempts to match given {@code prefix} and {@code suffix} in 1856 * the specified {@code text}. 1857 */ 1858 private boolean matchPrefixAndSuffix(String text, int position, String prefix, 1859 String matchedPrefix, String defaultPrefix, String suffix, 1860 String matchedSuffix, String defaultSuffix) { 1861 1862 // Check the compact pattern suffix only if there is a 1863 // compact prefix match or a default prefix match 1864 // because the compact prefix and suffix should match at the same 1865 // index to obtain the multiplier. 1866 // The prefix match is required because of the possibility of 1867 // same prefix at multiple index, in which case matching the suffix 1868 // is used to obtain the single match 1869 1870 if (prefix.equals(matchedPrefix) 1871 || matchedPrefix.equals(defaultPrefix)) { 1872 return matchAffix(text, position, suffix, defaultSuffix, matchedSuffix); 1873 } 1874 return false; 1875 } 1876 1877 /** 1878 * Computes multiplier by matching the given {@code matchedPrefix} 1879 * and suffix in the specified {@code text} from the lists of 1880 * prefixes and suffixes extracted from compact patterns. 1881 * 1882 * @param text the string to parse 1883 * @param parsePosition the {@code ParsePosition} object representing the 1884 * index and error index of the parse string 1885 * @param matchedPrefix prefix extracted which needs to be matched to 1886 * obtain the multiplier 1887 * @param status upon return contains boolean status flags indicating 1888 * whether the value is positive 1889 * @param gotPositive based on the prefix parsed; whether the number is positive 1890 * @param gotNegative based on the prefix parsed; whether the number is negative 1891 * @return the multiplier matching the prefix and suffix; -1 otherwise 1892 */ 1893 private Number computeParseMultiplier(String text, ParsePosition parsePosition, 1894 String matchedPrefix, boolean[] status, boolean gotPositive, 1895 boolean gotNegative, double num) { 1896 1897 int position = parsePosition.index; 1898 boolean gotPos = false; 1899 boolean gotNeg = false; 1900 int matchedPosIndex = -1; 1901 int matchedNegIndex = -1; 1902 String matchedPosSuffix = ""; 1903 String matchedNegSuffix = ""; 1904 for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { 1905 String positivePrefix = getAffix(true, true, false, compactIndex, (int)num); 1906 String negativePrefix = getAffix(true, true, true, compactIndex, (int)num); 1907 String positiveSuffix = getAffix(true, false, false, compactIndex, (int)num); 1908 String negativeSuffix = getAffix(true, false, true, compactIndex, (int)num); 1909 1910 // Do not break if a match occur; there is a possibility that the 1911 // subsequent affixes may match the longer subsequence in the given 1912 // string. 1913 // For example, matching "3Mdx" with "M", "Md" should match with "Md" 1914 boolean match = matchPrefixAndSuffix(text, position, positivePrefix, matchedPrefix, 1915 defaultDecimalFormat.getPositivePrefix(), positiveSuffix, 1916 matchedPosSuffix, defaultDecimalFormat.getPositiveSuffix()); 1917 if (match) { 1918 matchedPosIndex = compactIndex; 1919 matchedPosSuffix = positiveSuffix; 1920 gotPos = true; 1921 } 1922 1923 match = matchPrefixAndSuffix(text, position, negativePrefix, matchedPrefix, 1924 defaultDecimalFormat.getNegativePrefix(), negativeSuffix, 1925 matchedNegSuffix, defaultDecimalFormat.getNegativeSuffix()); 1926 if (match) { 1927 matchedNegIndex = compactIndex; 1928 matchedNegSuffix = negativeSuffix; 1929 gotNeg = true; 1930 } 1931 } 1932 1933 // Suffix in the given text does not match with the compact 1934 // patterns suffixes; match with the default suffix 1935 if (!gotPos && !gotNeg) { 1936 String positiveSuffix = defaultDecimalFormat.getPositiveSuffix(); 1937 String negativeSuffix = defaultDecimalFormat.getNegativeSuffix(); 1938 if (text.regionMatches(position, positiveSuffix, 0, 1939 positiveSuffix.length())) { 1940 // Matches the default positive prefix 1941 matchedPosSuffix = positiveSuffix; 1942 gotPos = true; 1943 } 1944 if (text.regionMatches(position, negativeSuffix, 0, 1945 negativeSuffix.length())) { 1946 // Matches the default negative suffix 1947 matchedNegSuffix = negativeSuffix; 1948 gotNeg = true; 1949 } 1950 } 1951 1952 // If both matches, take the longest one 1953 if (gotPos && gotNeg) { 1954 if (matchedPosSuffix.length() > matchedNegSuffix.length()) { 1955 gotNeg = false; 1956 } else if (matchedPosSuffix.length() < matchedNegSuffix.length()) { 1957 gotPos = false; 1958 } else { 1959 // If longest comparison fails; take the positive and negative 1960 // sign of matching prefix 1961 gotPos = gotPositive; 1962 gotNeg = gotNegative; 1963 } 1964 } 1965 1966 // Fail if neither or both 1967 if (gotPos == gotNeg) { 1968 parsePosition.errorIndex = position; 1969 return -1L; 1970 } 1971 1972 Number cnfMultiplier; 1973 // Update the parse position index and take compact multiplier 1974 // only if it matches the compact suffix, not the default 1975 // suffix; else multiplier should be 1 1976 if (gotPos) { 1977 parsePosition.index = position + matchedPosSuffix.length(); 1978 cnfMultiplier = matchedPosIndex != -1 1979 ? divisors.get(matchedPosIndex) : 1L; 1980 } else { 1981 parsePosition.index = position + matchedNegSuffix.length(); 1982 cnfMultiplier = matchedNegIndex != -1 1983 ? divisors.get(matchedNegIndex) : 1L; 1984 } 1985 status[STATUS_POSITIVE] = gotPos; 1986 return cnfMultiplier; 1987 } 1988 1989 /** 1990 * Reconstitutes this {@code CompactNumberFormat} from a stream 1991 * (that is, deserializes it) after performing some validations. 1992 * This method throws InvalidObjectException, if the stream data is invalid 1993 * because of the following reasons, 1994 * <ul> 1995 * <li> If any of the {@code decimalPattern}, {@code compactPatterns}, 1996 * {@code symbols} or {@code roundingMode} is {@code null}. 1997 * <li> If the {@code decimalPattern} or the {@code compactPatterns} array 1998 * contains an invalid pattern or if a {@code null} appears in the array of 1999 * compact patterns. 2000 * <li> If the {@code minimumIntegerDigits} is greater than the 2001 * {@code maximumIntegerDigits} or the {@code minimumFractionDigits} is 2002 * greater than the {@code maximumFractionDigits}. This check is performed 2003 * by superclass's Object. 2004 * <li> If any of the minimum/maximum integer/fraction digit count is 2005 * negative. This check is performed by superclass's readObject. 2006 * <li> If the minimum or maximum integer digit count is larger than 309 or 2007 * if the minimum or maximum fraction digit count is larger than 340. 2008 * <li> If the grouping size is negative or larger than 127. 2009 * </ul> 2010 * If the {@code pluralRules} field is not deserialized from the stream, it 2011 * will be set to an empty string. 2012 * 2013 * @param inStream the stream 2014 * @throws IOException if an I/O error occurs 2015 * @throws ClassNotFoundException if the class of a serialized object 2016 * could not be found 2017 */ 2018 @java.io.Serial 2019 private void readObject(ObjectInputStream inStream) throws IOException, 2020 ClassNotFoundException { 2021 2022 inStream.defaultReadObject(); 2023 if (decimalPattern == null || compactPatterns == null 2024 || symbols == null || roundingMode == null) { 2025 throw new InvalidObjectException("One of the 'decimalPattern'," 2026 + " 'compactPatterns', 'symbols' or 'roundingMode'" 2027 + " is null"); 2028 } 2029 2030 // Check only the maximum counts because NumberFormat.readObject has 2031 // already ensured that the maximum is greater than the minimum count. 2032 if (getMaximumIntegerDigits() > DecimalFormat.DOUBLE_INTEGER_DIGITS 2033 || getMaximumFractionDigits() > DecimalFormat.DOUBLE_FRACTION_DIGITS) { 2034 throw new InvalidObjectException("Digit count out of range"); 2035 } 2036 2037 // Check if the grouping size is negative, on an attempt to 2038 // put value > 127, it wraps around, so check just negative value 2039 if (groupingSize < 0) { 2040 throw new InvalidObjectException("Grouping size is negative"); 2041 } 2042 2043 // pluralRules is since 14. Fill in empty string if it is null 2044 if (pluralRules == null) { 2045 pluralRules = ""; 2046 } 2047 2048 try { 2049 processCompactPatterns(); 2050 } catch (IllegalArgumentException ex) { 2051 throw new InvalidObjectException(ex.getMessage()); 2052 } 2053 2054 decimalFormat = new DecimalFormat(SPECIAL_PATTERN, symbols); 2055 decimalFormat.setMaximumFractionDigits(getMaximumFractionDigits()); 2056 decimalFormat.setMinimumFractionDigits(getMinimumFractionDigits()); 2057 decimalFormat.setMaximumIntegerDigits(getMaximumIntegerDigits()); 2058 decimalFormat.setMinimumIntegerDigits(getMinimumIntegerDigits()); 2059 decimalFormat.setRoundingMode(getRoundingMode()); 2060 decimalFormat.setGroupingSize(getGroupingSize()); 2061 decimalFormat.setGroupingUsed(isGroupingUsed()); 2062 decimalFormat.setParseIntegerOnly(isParseIntegerOnly()); 2063 2064 try { 2065 defaultDecimalFormat = new DecimalFormat(decimalPattern, symbols); 2066 defaultDecimalFormat.setMaximumFractionDigits(0); 2067 } catch (IllegalArgumentException ex) { 2068 throw new InvalidObjectException(ex.getMessage()); 2069 } 2070 2071 } 2072 2073 /** 2074 * Sets the maximum number of digits allowed in the integer portion of a 2075 * number. 2076 * The maximum allowed integer range is 309, if the {@code newValue} > 309, 2077 * then the maximum integer digits count is set to 309. Negative input 2078 * values are replaced with 0. 2079 * 2080 * @param newValue the maximum number of integer digits to be shown 2081 * @see #getMaximumIntegerDigits() 2082 */ 2083 @Override 2084 public void setMaximumIntegerDigits(int newValue) { 2085 // The maximum integer digits is checked with the allowed range before calling 2086 // the DecimalFormat.setMaximumIntegerDigits, which performs the negative check 2087 // on the given newValue while setting it as max integer digits. 2088 // For example, if a negative value is specified, it is replaced with 0 2089 decimalFormat.setMaximumIntegerDigits(Math.min(newValue, 2090 DecimalFormat.DOUBLE_INTEGER_DIGITS)); 2091 super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 2092 if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) { 2093 decimalFormat.setMinimumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 2094 super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 2095 } 2096 } 2097 2098 /** 2099 * Sets the minimum number of digits allowed in the integer portion of a 2100 * number. 2101 * The maximum allowed integer range is 309, if the {@code newValue} > 309, 2102 * then the minimum integer digits count is set to 309. Negative input 2103 * values are replaced with 0. 2104 * 2105 * @param newValue the minimum number of integer digits to be shown 2106 * @see #getMinimumIntegerDigits() 2107 */ 2108 @Override 2109 public void setMinimumIntegerDigits(int newValue) { 2110 // The minimum integer digits is checked with the allowed range before calling 2111 // the DecimalFormat.setMinimumIntegerDigits, which performs check on the given 2112 // newValue while setting it as min integer digits. For example, if a negative 2113 // value is specified, it is replaced with 0 2114 decimalFormat.setMinimumIntegerDigits(Math.min(newValue, 2115 DecimalFormat.DOUBLE_INTEGER_DIGITS)); 2116 super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 2117 if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) { 2118 decimalFormat.setMaximumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 2119 super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 2120 } 2121 } 2122 2123 /** 2124 * Sets the minimum number of digits allowed in the fraction portion of a 2125 * number. 2126 * The maximum allowed fraction range is 340, if the {@code newValue} > 340, 2127 * then the minimum fraction digits count is set to 340. Negative input 2128 * values are replaced with 0. 2129 * 2130 * @param newValue the minimum number of fraction digits to be shown 2131 * @see #getMinimumFractionDigits() 2132 */ 2133 @Override 2134 public void setMinimumFractionDigits(int newValue) { 2135 // The minimum fraction digits is checked with the allowed range before 2136 // calling the DecimalFormat.setMinimumFractionDigits, which performs 2137 // check on the given newValue while setting it as min fraction 2138 // digits. For example, if a negative value is specified, it is 2139 // replaced with 0 2140 decimalFormat.setMinimumFractionDigits(Math.min(newValue, 2141 DecimalFormat.DOUBLE_FRACTION_DIGITS)); 2142 super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); 2143 if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) { 2144 decimalFormat.setMaximumFractionDigits(decimalFormat.getMinimumFractionDigits()); 2145 super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); 2146 } 2147 } 2148 2149 /** 2150 * Sets the maximum number of digits allowed in the fraction portion of a 2151 * number. 2152 * The maximum allowed fraction range is 340, if the {@code newValue} > 340, 2153 * then the maximum fraction digits count is set to 340. Negative input 2154 * values are replaced with 0. 2155 * 2156 * @param newValue the maximum number of fraction digits to be shown 2157 * @see #getMaximumFractionDigits() 2158 */ 2159 @Override 2160 public void setMaximumFractionDigits(int newValue) { 2161 // The maximum fraction digits is checked with the allowed range before 2162 // calling the DecimalFormat.setMaximumFractionDigits, which performs 2163 // check on the given newValue while setting it as max fraction digits. 2164 // For example, if a negative value is specified, it is replaced with 0 2165 decimalFormat.setMaximumFractionDigits(Math.min(newValue, 2166 DecimalFormat.DOUBLE_FRACTION_DIGITS)); 2167 super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); 2168 if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) { 2169 decimalFormat.setMinimumFractionDigits(decimalFormat.getMaximumFractionDigits()); 2170 super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); 2171 } 2172 } 2173 2174 /** 2175 * Gets the {@link java.math.RoundingMode} used in this 2176 * {@code CompactNumberFormat}. 2177 * 2178 * @return the {@code RoundingMode} used for this 2179 * {@code CompactNumberFormat} 2180 * @see #setRoundingMode(RoundingMode) 2181 */ 2182 @Override 2183 public RoundingMode getRoundingMode() { 2184 return roundingMode; 2185 } 2186 2187 /** 2188 * Sets the {@link java.math.RoundingMode} used in this 2189 * {@code CompactNumberFormat}. 2190 * 2191 * @param roundingMode the {@code RoundingMode} to be used 2192 * @see #getRoundingMode() 2193 * @throws NullPointerException if {@code roundingMode} is {@code null} 2194 */ 2195 @Override 2196 public void setRoundingMode(RoundingMode roundingMode) { 2197 decimalFormat.setRoundingMode(roundingMode); 2198 this.roundingMode = roundingMode; 2199 } 2200 2201 /** 2202 * Returns the grouping size. Grouping size is the number of digits between 2203 * grouping separators in the integer portion of a number. For example, 2204 * in the compact number {@code "12,347 trillion"} for the 2205 * {@link java.util.Locale#US US locale}, the grouping size is 3. 2206 * 2207 * @return the grouping size 2208 * @see #setGroupingSize 2209 * @see java.text.NumberFormat#isGroupingUsed 2210 * @see java.text.DecimalFormatSymbols#getGroupingSeparator 2211 */ 2212 public int getGroupingSize() { 2213 return groupingSize; 2214 } 2215 2216 /** 2217 * Sets the grouping size. Grouping size is the number of digits between 2218 * grouping separators in the integer portion of a number. For example, 2219 * in the compact number {@code "12,347 trillion"} for the 2220 * {@link java.util.Locale#US US locale}, the grouping size is 3. The grouping 2221 * size must be greater than or equal to zero and less than or equal to 127. 2222 * 2223 * @param newValue the new grouping size 2224 * @see #getGroupingSize 2225 * @see java.text.NumberFormat#setGroupingUsed 2226 * @see java.text.DecimalFormatSymbols#setGroupingSeparator 2227 * @throws IllegalArgumentException if {@code newValue} is negative or 2228 * larger than 127 2229 */ 2230 public void setGroupingSize(int newValue) { 2231 if (newValue < 0 || newValue > 127) { 2232 throw new IllegalArgumentException( 2233 "The value passed is negative or larger than 127"); 2234 } 2235 groupingSize = (byte) newValue; 2236 decimalFormat.setGroupingSize(groupingSize); 2237 } 2238 2239 /** 2240 * Returns true if grouping is used in this format. For example, with 2241 * grouping on and grouping size set to 3, the number {@code 12346567890987654} 2242 * can be formatted as {@code "12,347 trillion"} in the 2243 * {@link java.util.Locale#US US locale}. 2244 * The grouping separator is locale dependent. 2245 * 2246 * @return {@code true} if grouping is used; 2247 * {@code false} otherwise 2248 * @see #setGroupingUsed 2249 */ 2250 @Override 2251 public boolean isGroupingUsed() { 2252 return super.isGroupingUsed(); 2253 } 2254 2255 /** 2256 * Sets whether or not grouping will be used in this format. 2257 * 2258 * @param newValue {@code true} if grouping is used; 2259 * {@code false} otherwise 2260 * @see #isGroupingUsed 2261 */ 2262 @Override 2263 public void setGroupingUsed(boolean newValue) { 2264 decimalFormat.setGroupingUsed(newValue); 2265 super.setGroupingUsed(newValue); 2266 } 2267 2268 /** 2269 * Returns true if this format parses only an integer from the number 2270 * component of a compact number. 2271 * Parsing an integer means that only an integer is considered from the 2272 * number component, prefix/suffix is still considered to compute the 2273 * resulting output. 2274 * For example, in the {@link java.util.Locale#US US locale}, if this method 2275 * returns {@code true}, the string {@code "1234.78 thousand"} would be 2276 * parsed as the value {@code 1234000} (1234 (integer part) * 1000 2277 * (thousand)) and the fractional part would be skipped. 2278 * The exact format accepted by the parse operation is locale dependent. 2279 * 2280 * @return {@code true} if compact numbers should be parsed as integers 2281 * only; {@code false} otherwise 2282 */ 2283 @Override 2284 public boolean isParseIntegerOnly() { 2285 return super.isParseIntegerOnly(); 2286 } 2287 2288 /** 2289 * Sets whether or not this format parses only an integer from the number 2290 * component of a compact number. 2291 * 2292 * @param value {@code true} if compact numbers should be parsed as 2293 * integers only; {@code false} otherwise 2294 * @see #isParseIntegerOnly 2295 */ 2296 @Override 2297 public void setParseIntegerOnly(boolean value) { 2298 decimalFormat.setParseIntegerOnly(value); 2299 super.setParseIntegerOnly(value); 2300 } 2301 2302 /** 2303 * Returns whether the {@link #parse(String, ParsePosition)} 2304 * method returns {@code BigDecimal}. The default value is false. 2305 * 2306 * @return {@code true} if the parse method returns BigDecimal; 2307 * {@code false} otherwise 2308 * @see #setParseBigDecimal 2309 * 2310 */ 2311 public boolean isParseBigDecimal() { 2312 return parseBigDecimal; 2313 } 2314 2315 /** 2316 * Sets whether the {@link #parse(String, ParsePosition)} 2317 * method returns {@code BigDecimal}. 2318 * 2319 * @param newValue {@code true} if the parse method returns BigDecimal; 2320 * {@code false} otherwise 2321 * @see #isParseBigDecimal 2322 * 2323 */ 2324 public void setParseBigDecimal(boolean newValue) { 2325 parseBigDecimal = newValue; 2326 } 2327 2328 /** 2329 * Checks if this {@code CompactNumberFormat} is equal to the 2330 * specified {@code obj}. The objects of type {@code CompactNumberFormat} 2331 * are compared, other types return false; obeys the general contract of 2332 * {@link java.lang.Object#equals(java.lang.Object) Object.equals}. 2333 * 2334 * @param obj the object to compare with 2335 * @return true if this is equal to the other {@code CompactNumberFormat} 2336 */ 2337 @Override 2338 public boolean equals(Object obj) { 2339 2340 if (!super.equals(obj)) { 2341 return false; 2342 } 2343 2344 CompactNumberFormat other = (CompactNumberFormat) obj; 2345 return decimalPattern.equals(other.decimalPattern) 2346 && symbols.equals(other.symbols) 2347 && Arrays.equals(compactPatterns, other.compactPatterns) 2348 && roundingMode.equals(other.roundingMode) 2349 && pluralRules.equals(other.pluralRules) 2350 && groupingSize == other.groupingSize 2351 && parseBigDecimal == other.parseBigDecimal; 2352 } 2353 2354 /** 2355 * Returns the hash code for this {@code CompactNumberFormat} instance. 2356 * 2357 * @return hash code for this {@code CompactNumberFormat} 2358 */ 2359 @Override 2360 public int hashCode() { 2361 return 31 * super.hashCode() + 2362 Objects.hash(decimalPattern, symbols, roundingMode, pluralRules) 2363 + Arrays.hashCode(compactPatterns) + groupingSize 2364 + Boolean.hashCode(parseBigDecimal); 2365 } 2366 2367 /** 2368 * Creates and returns a copy of this {@code CompactNumberFormat} 2369 * instance. 2370 * 2371 * @return a clone of this instance 2372 */ 2373 @Override 2374 public CompactNumberFormat clone() { 2375 CompactNumberFormat other = (CompactNumberFormat) super.clone(); 2376 other.compactPatterns = compactPatterns.clone(); 2377 other.symbols = (DecimalFormatSymbols) symbols.clone(); 2378 return other; 2379 } 2380 2381 /** 2382 * Abstraction of affix or number (represented by zeros) patterns for each "count" tag. 2383 */ 2384 private final class Patterns { 2385 private final Map<String, String> patternsMap = new HashMap<>(); 2386 2387 void put(String count, String pattern) { 2388 patternsMap.put(count, pattern); 2389 } 2390 2391 String get(double num) { 2392 return patternsMap.getOrDefault(getPluralCategory(num), 2393 patternsMap.getOrDefault("other", "")); 2394 } 2395 2396 Patterns expandAffix() { 2397 Patterns ret = new Patterns(); 2398 patternsMap.forEach((key, value) -> ret.put(key, CompactNumberFormat.this.expandAffix(value))); 2399 return ret; 2400 } 2401 } 2402 2403 private int getIntegerPart(double number, double divisor) { 2404 return BigDecimal.valueOf(number) 2405 .divide(BigDecimal.valueOf(divisor), roundingMode).intValue(); 2406 } 2407 2408 /** 2409 * Returns LDML's tag from the plurals rules 2410 * 2411 * @param input input number in double type 2412 * @return LDML "count" tag 2413 */ 2414 private String getPluralCategory(double input) { 2415 if (rulesMap != null) { 2416 return rulesMap.entrySet().stream() 2417 .filter(e -> matchPluralRule(e.getValue(), input)) 2418 .map(Map.Entry::getKey) 2419 .findFirst() 2420 .orElse("other"); 2421 } 2422 2423 // defaults to "other" 2424 return "other"; 2425 } 2426 2427 private static boolean matchPluralRule(String condition, double input) { 2428 return Arrays.stream(condition.split("or")) 2429 .anyMatch(and_condition -> Arrays.stream(and_condition.split("and")) 2430 .allMatch(r -> relationCheck(r, input))); 2431 } 2432 2433 private final static String NAMED_EXPR = "(?<op>[niftvw])\\s*((?<div>[/%])\\s*(?<val>\\d+))*"; 2434 private final static String NAMED_RELATION = "(?<rel>!?=)"; 2435 private final static String NAMED_VALUE_RANGE = "(?<start>\\d+)\\.\\.(?<end>\\d+)|(?<value>\\d+)"; 2436 private final static Pattern EXPR_PATTERN = Pattern.compile(NAMED_EXPR); 2437 private final static Pattern RELATION_PATTERN = Pattern.compile(NAMED_RELATION); 2438 private final static Pattern VALUE_RANGE_PATTERN = Pattern.compile(NAMED_VALUE_RANGE); 2439 2440 /** 2441 * Checks if the 'input' equals the value, or within the range. 2442 * 2443 * @param valueOrRange A string representing either a single value or a range 2444 * @param input to examine in double 2445 * @return match indicator 2446 */ 2447 private static boolean valOrRangeMatches(String valueOrRange, double input) { 2448 Matcher m = VALUE_RANGE_PATTERN.matcher(valueOrRange); 2449 2450 if (m.find()) { 2451 String value = m.group("value"); 2452 if (value != null) { 2453 return input == Double.parseDouble(value); 2454 } else { 2455 return input >= Double.parseDouble(m.group("start")) && 2456 input <= Double.parseDouble(m.group("end")); 2457 } 2458 } 2459 2460 return false; 2461 } 2462 2463 /** 2464 * Checks if the input value satisfies the relation. Each possible value or range is 2465 * separated by a comma ',' 2466 * 2467 * @param relation relation string, e.g, "n = 1, 3..5", or "n != 1, 3..5" 2468 * @param input value to examine in double 2469 * @return boolean to indicate whether the relation satisfies or not. If the relation 2470 * is '=', true if any of the possible value/range satisfies. If the relation is '!=', 2471 * none of the possible value/range should satisfy to return true. 2472 */ 2473 private static boolean relationCheck(String relation, double input) { 2474 Matcher expr = EXPR_PATTERN.matcher(relation); 2475 2476 if (expr.find()) { 2477 double lop = evalLOperand(expr, input); 2478 Matcher rel = RELATION_PATTERN.matcher(relation); 2479 2480 if (rel.find(expr.end())) { 2481 var conditions = 2482 Arrays.stream(relation.substring(rel.end()).split(",")); 2483 2484 if (Objects.equals(rel.group("rel"), "!=")) { 2485 return conditions.noneMatch(c -> valOrRangeMatches(c, lop)); 2486 } else { 2487 return conditions.anyMatch(c -> valOrRangeMatches(c, lop)); 2488 } 2489 } 2490 } 2491 2492 return false; 2493 } 2494 2495 /** 2496 * Evaluates the left operand value. 2497 * 2498 * @param expr Match result 2499 * @param input value to examine in double 2500 * @return resulting double value 2501 */ 2502 private static double evalLOperand(Matcher expr, double input) { 2503 double ret = 0; 2504 2505 if (input == Double.POSITIVE_INFINITY) { 2506 ret =input; 2507 } else { 2508 String op = expr.group("op"); 2509 if (Objects.equals(op, "n") || Objects.equals(op, "i")) { 2510 ret = input; 2511 } 2512 2513 String divop = expr.group("div"); 2514 if (divop != null) { 2515 String divisor = expr.group("val"); 2516 switch (divop) { 2517 case "%" -> ret %= Double.parseDouble(divisor); 2518 case "/" -> ret /= Double.parseDouble(divisor); 2519 } 2520 } 2521 } 2522 2523 return ret; 2524 } 2525 }