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 * The {@code DecimalFormatSymbols} object used by this format. 267 * It contains the symbols used to format numbers. For example, 268 * the grouping separator, decimal separator, and so on. 269 * This field must not be {@code null}. 270 * 271 * @serial 272 * @see DecimalFormatSymbols 273 */ 274 private DecimalFormatSymbols symbols; 275 276 /** 277 * The decimal pattern which is used for formatting the numbers 278 * matching special pattern "0". This field must not be {@code null}. 279 * 280 * @serial 281 * @see DecimalFormat 282 */ 283 private final String decimalPattern; 284 285 /** 286 * A {@code DecimalFormat} used by this format for getting corresponding 287 * general number formatting behavior for compact numbers. 288 * 289 */ 290 private transient DecimalFormat decimalFormat; 291 292 /** 293 * A {@code DecimalFormat} used by this format for getting general number 294 * formatting behavior for the numbers which can't be represented as compact 295 * numbers. For example, number matching the special pattern "0" are 296 * formatted through general number format pattern provided by 297 * {@link java.text.DecimalFormat DecimalFormat} 298 * for the specified locale. 299 * 300 */ 301 private transient DecimalFormat defaultDecimalFormat; 302 303 /** 304 * The number of digits between grouping separators in the integer portion 305 * of a compact number. For the grouping to work while formatting, this 306 * field needs to be greater than 0 with grouping used set as true. 307 * This field must not be negative. 308 * 309 * @serial 310 */ 311 private byte groupingSize = 0; 312 313 /** 314 * Returns whether the {@link #parse(String, ParsePosition)} 315 * method returns {@code BigDecimal}. 316 * 317 * @serial 318 */ 319 private boolean parseBigDecimal = false; 320 321 /** 322 * The {@code RoundingMode} used in this compact number format. 323 * This field must not be {@code null}. 324 * 325 * @serial 326 */ 327 private RoundingMode roundingMode = RoundingMode.HALF_EVEN; 328 329 /** 330 * The {@code pluralRules} used in this compact number format. 331 * {@code pluralRules} is a String designating plural rules which associate 332 * the {@code Count} keyword, such as "{@code one}", and the 333 * actual integer number. Its syntax is defined in Unicode Consortium's 334 * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax"> 335 * Plural rules syntax</a>. 336 * The default value is an empty string, meaning there is no plural rules. 337 * 338 * @serial 339 * @since 14 340 */ 341 private String pluralRules = ""; 342 343 /** 344 * The map for plural rules that maps LDML defined tags (e.g. "one") to 345 * its rule. 346 */ 347 private transient Map<String, String> rulesMap; 348 349 /** 350 * Special pattern used for compact numbers 351 */ 352 private static final String SPECIAL_PATTERN = "0"; 353 354 /** 355 * Multiplier for compact pattern range. In 356 * the list compact patterns each compact pattern 357 * specify the range with the multiplication factor of 10 358 * of its previous compact pattern range. 359 * For example, 10^0, 10^1, 10^2, 10^3, 10^4... 360 * 361 */ 362 private static final int RANGE_MULTIPLIER = 10; 363 364 /** 365 * Creates a {@code CompactNumberFormat} using the given decimal pattern, 366 * decimal format symbols and compact patterns. 367 * To obtain the instance of {@code CompactNumberFormat} with the standard 368 * compact patterns for a {@code Locale} and {@code Style}, 369 * it is recommended to use the factory methods given by 370 * {@code NumberFormat} for compact number formatting. For example, 371 * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. 372 * 373 * @param decimalPattern a decimal pattern for general number formatting 374 * @param symbols the set of symbols to be used 375 * @param compactPatterns an array of 376 * <a href = "CompactNumberFormat.html#compact_number_patterns"> 377 * compact number patterns</a> 378 * @throws NullPointerException if any of the given arguments is 379 * {@code null} 380 * @throws IllegalArgumentException if the given {@code decimalPattern} or the 381 * {@code compactPatterns} array contains an invalid pattern 382 * or if a {@code null} appears in the array of compact 383 * patterns 384 * @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols) 385 * @see DecimalFormatSymbols 386 */ 387 public CompactNumberFormat(String decimalPattern, 388 DecimalFormatSymbols symbols, String[] compactPatterns) { 389 this(decimalPattern, symbols, compactPatterns, ""); 390 } 391 392 /** 393 * Creates a {@code CompactNumberFormat} using the given decimal pattern, 394 * decimal format symbols, compact patterns, and plural rules. 395 * To obtain the instance of {@code CompactNumberFormat} with the standard 396 * compact patterns for a {@code Locale}, {@code Style}, and {@code pluralRules}, 397 * it is recommended to use the factory methods given by 398 * {@code NumberFormat} for compact number formatting. For example, 399 * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. 400 * 401 * @param decimalPattern a decimal pattern for general number formatting 402 * @param symbols the set of symbols to be used 403 * @param compactPatterns an array of 404 * <a href = "CompactNumberFormat.html#compact_number_patterns"> 405 * compact number patterns</a> 406 * @param pluralRules a String designating plural rules which associate 407 * the {@code Count} keyword, such as "{@code one}", and the 408 * actual integer number. Its syntax is defined in Unicode Consortium's 409 * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax"> 410 * Plural rules syntax</a> 411 * @throws NullPointerException if any of the given arguments is 412 * {@code null} 413 * @throws IllegalArgumentException if the given {@code decimalPattern}, 414 * the {@code compactPatterns} array contains an invalid pattern, 415 * a {@code null} appears in the array of compact patterns, 416 * or if the given {@code pluralRules} contains an invalid syntax 417 * @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols) 418 * @see DecimalFormatSymbols 419 * @since 14 420 */ 421 public CompactNumberFormat(String decimalPattern, 422 DecimalFormatSymbols symbols, String[] compactPatterns, 423 String pluralRules) { 424 425 Objects.requireNonNull(decimalPattern, "decimalPattern"); 426 Objects.requireNonNull(symbols, "symbols"); 427 Objects.requireNonNull(compactPatterns, "compactPatterns"); 428 Objects.requireNonNull(pluralRules, "pluralRules"); 429 430 this.symbols = symbols; 431 // Instantiating the DecimalFormat with "0" pattern; this acts just as a 432 // basic pattern; the properties (For example, prefix/suffix) 433 // are later computed based on the compact number formatting process. 434 decimalFormat = new DecimalFormat(SPECIAL_PATTERN, this.symbols); 435 436 // Initializing the super class state with the decimalFormat values 437 // to represent this CompactNumberFormat. 438 // For setting the digits counts, use overridden setXXX methods of this 439 // CompactNumberFormat, as it performs check with the max range allowed 440 // for compact number formatting 441 setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 442 setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 443 setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); 444 setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); 445 446 super.setGroupingUsed(decimalFormat.isGroupingUsed()); 447 super.setParseIntegerOnly(decimalFormat.isParseIntegerOnly()); 448 449 this.compactPatterns = compactPatterns; 450 451 // DecimalFormat used for formatting numbers with special pattern "0". 452 // Formatting is delegated to the DecimalFormat's number formatting 453 // with no fraction digits 454 this.decimalPattern = decimalPattern; 455 defaultDecimalFormat = new DecimalFormat(this.decimalPattern, 456 this.symbols); 457 defaultDecimalFormat.setMaximumFractionDigits(0); 458 459 this.pluralRules = pluralRules; 460 461 // Process compact patterns to extract the prefixes, suffixes and 462 // divisors 463 processCompactPatterns(); 464 } 465 466 /** 467 * Formats a number to produce a string representing its compact form. 468 * The number can be of any subclass of {@link java.lang.Number}. 469 * @param number the number to format 470 * @param toAppendTo the {@code StringBuffer} to which the formatted 471 * text is to be appended 472 * @param fieldPosition keeps track on the position of the field within 473 * the returned string. For example, for formatting 474 * a number {@code 123456789} in the 475 * {@link java.util.Locale#US US locale}, 476 * if the given {@code fieldPosition} is 477 * {@link NumberFormat#INTEGER_FIELD}, the begin 478 * index and end index of {@code fieldPosition} 479 * will be set to 0 and 3, respectively for the 480 * output string {@code 123M}. Similarly, positions 481 * of the prefix and the suffix fields can be 482 * obtained using {@link NumberFormat.Field#PREFIX} 483 * and {@link NumberFormat.Field#SUFFIX} respectively. 484 * @return the {@code StringBuffer} passed in as {@code toAppendTo} 485 * @throws IllegalArgumentException if {@code number} is 486 * {@code null} or not an instance of {@code Number} 487 * @throws NullPointerException if {@code toAppendTo} or 488 * {@code fieldPosition} is {@code null} 489 * @throws ArithmeticException if rounding is needed with rounding 490 * mode being set to {@code RoundingMode.UNNECESSARY} 491 * @see FieldPosition 492 */ 493 @Override 494 public final StringBuffer format(Object number, 495 StringBuffer toAppendTo, 496 FieldPosition fieldPosition) { 497 498 if (number == null) { 499 throw new IllegalArgumentException("Cannot format null as a number"); 500 } 501 502 if (number instanceof Long || number instanceof Integer 503 || number instanceof Short || number instanceof Byte 504 || number instanceof AtomicInteger 505 || number instanceof AtomicLong 506 || (number instanceof BigInteger 507 && ((BigInteger) number).bitLength() < 64)) { 508 return format(((Number) number).longValue(), toAppendTo, 509 fieldPosition); 510 } else if (number instanceof BigDecimal) { 511 return format((BigDecimal) number, toAppendTo, fieldPosition); 512 } else if (number instanceof BigInteger) { 513 return format((BigInteger) number, toAppendTo, fieldPosition); 514 } else if (number instanceof Number) { 515 return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); 516 } else { 517 throw new IllegalArgumentException("Cannot format " 518 + number.getClass().getName() + " as a number"); 519 } 520 } 521 522 /** 523 * Formats a double to produce a string representing its compact form. 524 * @param number the double number to format 525 * @param result where the text is to be appended 526 * @param fieldPosition keeps track on the position of the field within 527 * the returned string. For example, to format 528 * a number {@code 1234567.89} in the 529 * {@link java.util.Locale#US US locale} 530 * if the given {@code fieldPosition} is 531 * {@link NumberFormat#INTEGER_FIELD}, the begin 532 * index and end index of {@code fieldPosition} 533 * will be set to 0 and 1, respectively for the 534 * output string {@code 1M}. Similarly, positions 535 * of the prefix and the suffix fields can be 536 * obtained using {@link NumberFormat.Field#PREFIX} 537 * and {@link NumberFormat.Field#SUFFIX} respectively. 538 * @return the {@code StringBuffer} passed in as {@code result} 539 * @throws NullPointerException if {@code result} or 540 * {@code fieldPosition} is {@code null} 541 * @throws ArithmeticException if rounding is needed with rounding 542 * mode being set to {@code RoundingMode.UNNECESSARY} 543 * @see FieldPosition 544 */ 545 @Override 546 public StringBuffer format(double number, StringBuffer result, 547 FieldPosition fieldPosition) { 548 549 fieldPosition.setBeginIndex(0); 550 fieldPosition.setEndIndex(0); 551 return format(number, result, fieldPosition.getFieldDelegate()); 552 } 553 554 private StringBuffer format(double number, StringBuffer result, 555 FieldDelegate delegate) { 556 557 boolean nanOrInfinity = decimalFormat.handleNaN(number, result, delegate); 558 if (nanOrInfinity) { 559 return result; 560 } 561 562 boolean isNegative = ((number < 0.0) 563 || (number == 0.0 && 1 / number < 0.0)); 564 565 nanOrInfinity = decimalFormat.handleInfinity(number, result, delegate, isNegative); 566 if (nanOrInfinity) { 567 return result; 568 } 569 570 // Round the double value with min fraction digits, the integer 571 // part of the rounded value is used for matching the compact 572 // number pattern 573 // For example, if roundingMode is HALF_UP with min fraction 574 // digits = 0, the number 999.6 should round up 575 // to 1000 and outputs 1K/thousand in "en_US" locale 576 DigitList dList = new DigitList(); 577 dList.setRoundingMode(getRoundingMode()); 578 number = isNegative ? -number : number; 579 dList.set(isNegative, number, getMinimumFractionDigits()); 580 581 double roundedNumber = dList.getDouble(); 582 int compactDataIndex = selectCompactPattern((long) roundedNumber); 583 if (compactDataIndex != -1) { 584 long divisor = (Long) divisors.get(compactDataIndex); 585 int iPart = getIntegerPart(number, divisor); 586 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 587 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 588 589 if (!prefix.isEmpty() || !suffix.isEmpty()) { 590 appendPrefix(result, prefix, delegate); 591 if (divisor > 0) { 592 roundedNumber = roundedNumber / divisor; 593 decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits()); 594 decimalFormat.subformatNumber(result, delegate, isNegative, 595 false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), 596 getMaximumFractionDigits(), getMinimumFractionDigits()); 597 appendSuffix(result, suffix, delegate); 598 } 599 } else { 600 defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative); 601 } 602 } else { 603 defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative); 604 } 605 return result; 606 } 607 608 /** 609 * Formats a long to produce a string representing its compact form. 610 * @param number the long number to format 611 * @param result where the text is to be appended 612 * @param fieldPosition keeps track on the position of the field within 613 * the returned string. For example, to format 614 * a number {@code 123456789} in the 615 * {@link java.util.Locale#US US locale}, 616 * if the given {@code fieldPosition} is 617 * {@link NumberFormat#INTEGER_FIELD}, the begin 618 * index and end index of {@code fieldPosition} 619 * will be set to 0 and 3, respectively for the 620 * output string {@code 123M}. Similarly, positions 621 * of the prefix and the suffix fields can be 622 * obtained using {@link NumberFormat.Field#PREFIX} 623 * and {@link NumberFormat.Field#SUFFIX} respectively. 624 * @return the {@code StringBuffer} passed in as {@code result} 625 * @throws NullPointerException if {@code result} or 626 * {@code fieldPosition} is {@code null} 627 * @throws ArithmeticException if rounding is needed with rounding 628 * mode being set to {@code RoundingMode.UNNECESSARY} 629 * @see FieldPosition 630 */ 631 @Override 632 public StringBuffer format(long number, StringBuffer result, 633 FieldPosition fieldPosition) { 634 635 fieldPosition.setBeginIndex(0); 636 fieldPosition.setEndIndex(0); 637 return format(number, result, fieldPosition.getFieldDelegate()); 638 } 639 640 private StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) { 641 boolean isNegative = (number < 0); 642 if (isNegative) { 643 number = -number; 644 } 645 646 if (number < 0) { // LONG_MIN 647 BigInteger bigIntegerValue = BigInteger.valueOf(number); 648 return format(bigIntegerValue, result, delegate, true); 649 } 650 651 int compactDataIndex = selectCompactPattern(number); 652 if (compactDataIndex != -1) { 653 long divisor = (Long) divisors.get(compactDataIndex); 654 int iPart = getIntegerPart(number, divisor); 655 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 656 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 657 if (!prefix.isEmpty() || !suffix.isEmpty()) { 658 appendPrefix(result, prefix, delegate); 659 if (divisor > 0) { 660 if ((number % divisor == 0)) { 661 number = number / divisor; 662 decimalFormat.setDigitList(number, isNegative, 0); 663 decimalFormat.subformatNumber(result, delegate, 664 isNegative, true, getMaximumIntegerDigits(), 665 getMinimumIntegerDigits(), getMaximumFractionDigits(), 666 getMinimumFractionDigits()); 667 } else { 668 // To avoid truncation of fractional part store 669 // the value in double and follow double path instead of 670 // long path 671 double dNumber = (double) number / divisor; 672 decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits()); 673 decimalFormat.subformatNumber(result, delegate, 674 isNegative, false, getMaximumIntegerDigits(), 675 getMinimumIntegerDigits(), getMaximumFractionDigits(), 676 getMinimumFractionDigits()); 677 } 678 appendSuffix(result, suffix, delegate); 679 } 680 } else { 681 number = isNegative ? -number : number; 682 defaultDecimalFormat.format(number, result, delegate); 683 } 684 } else { 685 number = isNegative ? -number : number; 686 defaultDecimalFormat.format(number, result, delegate); 687 } 688 return result; 689 } 690 691 /** 692 * Formats a BigDecimal to produce a string representing its compact form. 693 * @param number the BigDecimal number to format 694 * @param result where the text is to be appended 695 * @param fieldPosition keeps track on the position of the field within 696 * the returned string. For example, to format 697 * a number {@code 1234567.89} in the 698 * {@link java.util.Locale#US US locale}, 699 * if the given {@code fieldPosition} is 700 * {@link NumberFormat#INTEGER_FIELD}, the begin 701 * index and end index of {@code fieldPosition} 702 * will be set to 0 and 1, respectively for the 703 * output string {@code 1M}. Similarly, positions 704 * of the prefix and the suffix fields can be 705 * obtained using {@link NumberFormat.Field#PREFIX} 706 * and {@link NumberFormat.Field#SUFFIX} respectively. 707 * @return the {@code StringBuffer} passed in as {@code result} 708 * @throws ArithmeticException if rounding is needed with rounding 709 * mode being set to {@code RoundingMode.UNNECESSARY} 710 * @throws NullPointerException if any of the given parameter 711 * is {@code null} 712 * @see FieldPosition 713 */ 714 private StringBuffer format(BigDecimal number, StringBuffer result, 715 FieldPosition fieldPosition) { 716 717 Objects.requireNonNull(number); 718 fieldPosition.setBeginIndex(0); 719 fieldPosition.setEndIndex(0); 720 return format(number, result, fieldPosition.getFieldDelegate()); 721 } 722 723 private StringBuffer format(BigDecimal number, StringBuffer result, 724 FieldDelegate delegate) { 725 726 boolean isNegative = number.signum() == -1; 727 if (isNegative) { 728 number = number.negate(); 729 } 730 731 // Round the value with min fraction digits, the integer 732 // part of the rounded value is used for matching the compact 733 // number pattern 734 // For example, If roundingMode is HALF_UP with min fraction digits = 0, 735 // the number 999.6 should round up 736 // to 1000 and outputs 1K/thousand in "en_US" locale 737 number = number.setScale(getMinimumFractionDigits(), getRoundingMode()); 738 739 int compactDataIndex; 740 if (number.toBigInteger().bitLength() < 64) { 741 long longNumber = number.toBigInteger().longValue(); 742 compactDataIndex = selectCompactPattern(longNumber); 743 } else { 744 compactDataIndex = selectCompactPattern(number.toBigInteger()); 745 } 746 747 if (compactDataIndex != -1) { 748 Number divisor = divisors.get(compactDataIndex); 749 int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); 750 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 751 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 752 if (!prefix.isEmpty() || !suffix.isEmpty()) { 753 appendPrefix(result, prefix, delegate); 754 if (divisor.doubleValue() > 0) { 755 number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode()); 756 decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits()); 757 decimalFormat.subformatNumber(result, delegate, isNegative, 758 false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), 759 getMaximumFractionDigits(), getMinimumFractionDigits()); 760 appendSuffix(result, suffix, delegate); 761 } 762 } else { 763 number = isNegative ? number.negate() : number; 764 defaultDecimalFormat.format(number, result, delegate); 765 } 766 } else { 767 number = isNegative ? number.negate() : number; 768 defaultDecimalFormat.format(number, result, delegate); 769 } 770 return result; 771 } 772 773 /** 774 * Formats a BigInteger to produce a string representing its compact form. 775 * @param number the BigInteger number to format 776 * @param result where the text is to be appended 777 * @param fieldPosition keeps track on the position of the field within 778 * the returned string. For example, to format 779 * a number {@code 123456789} in the 780 * {@link java.util.Locale#US US locale}, 781 * if the given {@code fieldPosition} is 782 * {@link NumberFormat#INTEGER_FIELD}, the begin index 783 * and end index of {@code fieldPosition} will be set 784 * to 0 and 3, respectively for the output string 785 * {@code 123M}. Similarly, positions of the 786 * prefix and the suffix fields can be obtained 787 * using {@link NumberFormat.Field#PREFIX} and 788 * {@link NumberFormat.Field#SUFFIX} respectively. 789 * @return the {@code StringBuffer} passed in as {@code result} 790 * @throws ArithmeticException if rounding is needed with rounding 791 * mode being set to {@code RoundingMode.UNNECESSARY} 792 * @throws NullPointerException if any of the given parameter 793 * is {@code null} 794 * @see FieldPosition 795 */ 796 private StringBuffer format(BigInteger number, StringBuffer result, 797 FieldPosition fieldPosition) { 798 799 Objects.requireNonNull(number); 800 fieldPosition.setBeginIndex(0); 801 fieldPosition.setEndIndex(0); 802 return format(number, result, fieldPosition.getFieldDelegate(), false); 803 } 804 805 private StringBuffer format(BigInteger number, StringBuffer result, 806 FieldDelegate delegate, boolean formatLong) { 807 808 boolean isNegative = number.signum() == -1; 809 if (isNegative) { 810 number = number.negate(); 811 } 812 813 int compactDataIndex = selectCompactPattern(number); 814 if (compactDataIndex != -1) { 815 Number divisor = divisors.get(compactDataIndex); 816 int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue()); 817 String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart); 818 String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart); 819 if (!prefix.isEmpty() || !suffix.isEmpty()) { 820 appendPrefix(result, prefix, delegate); 821 if (divisor.doubleValue() > 0) { 822 if (number.mod(new BigInteger(divisor.toString())) 823 .compareTo(BigInteger.ZERO) == 0) { 824 number = number.divide(new BigInteger(divisor.toString())); 825 826 decimalFormat.setDigitList(number, isNegative, 0); 827 decimalFormat.subformatNumber(result, delegate, 828 isNegative, true, getMaximumIntegerDigits(), 829 getMinimumIntegerDigits(), getMaximumFractionDigits(), 830 getMinimumFractionDigits()); 831 } else { 832 // To avoid truncation of fractional part store the value in 833 // BigDecimal and follow BigDecimal path instead of 834 // BigInteger path 835 BigDecimal nDecimal = new BigDecimal(number) 836 .divide(new BigDecimal(divisor.toString()), getRoundingMode()); 837 decimalFormat.setDigitList(nDecimal, isNegative, getMaximumFractionDigits()); 838 decimalFormat.subformatNumber(result, delegate, 839 isNegative, false, getMaximumIntegerDigits(), 840 getMinimumIntegerDigits(), getMaximumFractionDigits(), 841 getMinimumFractionDigits()); 842 } 843 appendSuffix(result, suffix, delegate); 844 } 845 } else { 846 number = isNegative ? number.negate() : number; 847 defaultDecimalFormat.format(number, result, delegate, formatLong); 848 } 849 } else { 850 number = isNegative ? number.negate() : number; 851 defaultDecimalFormat.format(number, result, delegate, formatLong); 852 } 853 return result; 854 } 855 856 /** 857 * Obtain the designated affix from the appropriate list of affixes, 858 * based on the given arguments. 859 */ 860 private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative, int compactDataIndex, int iPart) { 861 return (isExpanded ? (isPrefix ? (isNegative ? negativePrefixes : positivePrefixes) : 862 (isNegative ? negativeSuffixes : positiveSuffixes)) : 863 (isPrefix ? (isNegative ? negativePrefixPatterns : positivePrefixPatterns) : 864 (isNegative ? negativeSuffixPatterns : positiveSuffixPatterns))) 865 .get(compactDataIndex).get(iPart); 866 } 867 868 /** 869 * Appends the {@code prefix} to the {@code result} and also set the 870 * {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.PREFIX} 871 * field positions. 872 * @param result the resulting string, where the pefix is to be appended 873 * @param prefix prefix to append 874 * @param delegate notified of the locations of 875 * {@code NumberFormat.Field.SIGN} and 876 * {@code NumberFormat.Field.PREFIX} fields 877 */ 878 private void appendPrefix(StringBuffer result, String prefix, 879 FieldDelegate delegate) { 880 append(result, expandAffix(prefix), delegate, 881 getFieldPositions(prefix, NumberFormat.Field.PREFIX)); 882 } 883 884 /** 885 * Appends {@code suffix} to the {@code result} and also set the 886 * {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.SUFFIX} 887 * field positions. 888 * @param result the resulting string, where the suffix is to be appended 889 * @param suffix suffix to append 890 * @param delegate notified of the locations of 891 * {@code NumberFormat.Field.SIGN} and 892 * {@code NumberFormat.Field.SUFFIX} fields 893 */ 894 private void appendSuffix(StringBuffer result, String suffix, 895 FieldDelegate delegate) { 896 append(result, expandAffix(suffix), delegate, 897 getFieldPositions(suffix, NumberFormat.Field.SUFFIX)); 898 } 899 900 /** 901 * Appends the {@code string} to the {@code result}. 902 * {@code delegate} is notified of SIGN, PREFIX and/or SUFFIX 903 * field positions. 904 * @param result the resulting string, where the text is to be appended 905 * @param string the text to append 906 * @param delegate notified of the locations of sub fields 907 * @param positions a list of {@code FieldPostion} in the given 908 * string 909 */ 910 private void append(StringBuffer result, String string, 911 FieldDelegate delegate, List<FieldPosition> positions) { 912 if (!string.isEmpty()) { 913 int start = result.length(); 914 result.append(string); 915 for (FieldPosition fp : positions) { 916 Format.Field attribute = fp.getFieldAttribute(); 917 delegate.formatted(attribute, attribute, 918 start + fp.getBeginIndex(), 919 start + fp.getEndIndex(), result); 920 } 921 } 922 } 923 924 /** 925 * Expands an affix {@code pattern} into a string of literals. 926 * All characters in the pattern are literals unless prefixed by QUOTE. 927 * The character prefixed by QUOTE is replaced with its respective 928 * localized literal. 929 * @param pattern a compact number pattern affix 930 * @return an expanded affix 931 */ 932 private String expandAffix(String pattern) { 933 // Return if no quoted character exists 934 if (pattern.indexOf(QUOTE) < 0) { 935 return pattern; 936 } 937 StringBuilder sb = new StringBuilder(); 938 for (int index = 0; index < pattern.length();) { 939 char ch = pattern.charAt(index++); 940 if (ch == QUOTE) { 941 ch = pattern.charAt(index++); 942 if (ch == MINUS_SIGN) { 943 sb.append(symbols.getMinusSignText()); 944 continue; 945 } 946 } 947 sb.append(ch); 948 } 949 return sb.toString(); 950 } 951 952 /** 953 * Returns a list of {@code FieldPostion} in the given {@code pattern}. 954 * @param pattern the pattern to be parsed for {@code FieldPosition} 955 * @param field whether a PREFIX or SUFFIX field 956 * @return a list of {@code FieldPostion} 957 */ 958 private List<FieldPosition> getFieldPositions(String pattern, Field field) { 959 List<FieldPosition> positions = new ArrayList<>(); 960 StringBuilder affix = new StringBuilder(); 961 int stringIndex = 0; 962 for (int index = 0; index < pattern.length();) { 963 char ch = pattern.charAt(index++); 964 if (ch == QUOTE) { 965 ch = pattern.charAt(index++); 966 if (ch == MINUS_SIGN) { 967 String minusText = symbols.getMinusSignText(); 968 FieldPosition fp = new FieldPosition(NumberFormat.Field.SIGN); 969 fp.setBeginIndex(stringIndex); 970 fp.setEndIndex(stringIndex + minusText.length()); 971 positions.add(fp); 972 stringIndex += minusText.length(); 973 affix.append(minusText); 974 continue; 975 } 976 } 977 stringIndex++; 978 affix.append(ch); 979 } 980 if (affix.length() != 0) { 981 FieldPosition fp = new FieldPosition(field); 982 fp.setBeginIndex(0); 983 fp.setEndIndex(affix.length()); 984 positions.add(fp); 985 } 986 return positions; 987 } 988 989 /** 990 * Select the index of the matched compact number pattern for 991 * the given {@code long} {@code number}. 992 * 993 * @param number number to be formatted 994 * @return index of matched compact pattern; 995 * -1 if no compact patterns specified 996 */ 997 private int selectCompactPattern(long number) { 998 999 if (compactPatterns.length == 0) { 1000 return -1; 1001 } 1002 1003 // Minimum index can be "0", max index can be "size - 1" 1004 int dataIndex = number <= 1 ? 0 : (int) Math.log10(number); 1005 dataIndex = Math.min(dataIndex, compactPatterns.length - 1); 1006 return dataIndex; 1007 } 1008 1009 /** 1010 * Select the index of the matched compact number 1011 * pattern for the given {@code BigInteger} {@code number}. 1012 * 1013 * @param number number to be formatted 1014 * @return index of matched compact pattern; 1015 * -1 if no compact patterns specified 1016 */ 1017 private int selectCompactPattern(BigInteger number) { 1018 1019 int matchedIndex = -1; 1020 if (compactPatterns.length == 0) { 1021 return matchedIndex; 1022 } 1023 1024 BigInteger currentValue = BigInteger.ONE; 1025 1026 // For formatting a number, the greatest type less than 1027 // or equal to number is used 1028 for (int index = 0; index < compactPatterns.length; index++) { 1029 if (number.compareTo(currentValue) > 0) { 1030 // Input number is greater than current type; try matching with 1031 // the next 1032 matchedIndex = index; 1033 currentValue = currentValue.multiply(BigInteger.valueOf(RANGE_MULTIPLIER)); 1034 continue; 1035 } 1036 if (number.compareTo(currentValue) < 0) { 1037 // Current type is greater than the input number; 1038 // take the previous pattern 1039 break; 1040 } else { 1041 // Equal 1042 matchedIndex = index; 1043 break; 1044 } 1045 } 1046 return matchedIndex; 1047 } 1048 1049 /** 1050 * Formats an Object producing an {@code AttributedCharacterIterator}. 1051 * The returned {@code AttributedCharacterIterator} can be used 1052 * to build the resulting string, as well as to determine information 1053 * about the resulting string. 1054 * <p> 1055 * Each attribute key of the {@code AttributedCharacterIterator} will 1056 * be of type {@code NumberFormat.Field}, with the attribute value 1057 * being the same as the attribute key. The prefix and the suffix 1058 * parts of the returned iterator (if present) are represented by 1059 * the attributes {@link NumberFormat.Field#PREFIX} and 1060 * {@link NumberFormat.Field#SUFFIX} respectively. 1061 * 1062 * 1063 * @throws NullPointerException if obj is null 1064 * @throws IllegalArgumentException when the Format cannot format the 1065 * given object 1066 * @throws ArithmeticException if rounding is needed with rounding 1067 * mode being set to {@code RoundingMode.UNNECESSARY} 1068 * @param obj The object to format 1069 * @return an {@code AttributedCharacterIterator} describing the 1070 * formatted value 1071 */ 1072 @Override 1073 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { 1074 CharacterIteratorFieldDelegate delegate 1075 = new CharacterIteratorFieldDelegate(); 1076 StringBuffer sb = new StringBuffer(); 1077 1078 if (obj instanceof Double || obj instanceof Float) { 1079 format(((Number) obj).doubleValue(), sb, delegate); 1080 } else if (obj instanceof Long || obj instanceof Integer 1081 || obj instanceof Short || obj instanceof Byte 1082 || obj instanceof AtomicInteger || obj instanceof AtomicLong) { 1083 format(((Number) obj).longValue(), sb, delegate); 1084 } else if (obj instanceof BigDecimal) { 1085 format((BigDecimal) obj, sb, delegate); 1086 } else if (obj instanceof BigInteger) { 1087 format((BigInteger) obj, sb, delegate, false); 1088 } else if (obj == null) { 1089 throw new NullPointerException( 1090 "formatToCharacterIterator must be passed non-null object"); 1091 } else { 1092 throw new IllegalArgumentException( 1093 "Cannot format given Object as a Number"); 1094 } 1095 return delegate.getIterator(sb.toString()); 1096 } 1097 1098 /** 1099 * Computes the divisor using minimum integer digits and 1100 * matched pattern index. If minIntDigits is empty, the divisor 1101 * will be negated. 1102 * @param minIntDigits string of 0s in compact pattern 1103 * @param patternIndex index of matched compact pattern 1104 * @return divisor value for the number matching the compact 1105 * pattern at given {@code patternIndex} 1106 */ 1107 private Number computeDivisor(String minIntDigits, int patternIndex) { 1108 int count = minIntDigits.length(); 1109 Number matchedValue; 1110 // The divisor value can go above long range, if the compact patterns 1111 // goes above index 18, divisor may need to be stored as BigInteger, 1112 // since long can't store numbers >= 10^19, 1113 if (patternIndex < 19) { 1114 matchedValue = (long) Math.pow(RANGE_MULTIPLIER, patternIndex); 1115 } else { 1116 matchedValue = BigInteger.valueOf(RANGE_MULTIPLIER).pow(patternIndex); 1117 } 1118 Number divisor = matchedValue; 1119 if (count > 0) { 1120 if (matchedValue instanceof BigInteger) { 1121 BigInteger bigValue = (BigInteger) matchedValue; 1122 if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1))) < 0) { 1123 throw new IllegalArgumentException("Invalid Pattern" 1124 + " [" + compactPatterns[patternIndex] 1125 + "]: min integer digits specified exceeds the limit" 1126 + " for the index " + patternIndex); 1127 } 1128 divisor = bigValue.divide(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1))); 1129 } else { 1130 long longValue = (long) matchedValue; 1131 if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count - 1)) { 1132 throw new IllegalArgumentException("Invalid Pattern" 1133 + " [" + compactPatterns[patternIndex] 1134 + "]: min integer digits specified exceeds the limit" 1135 + " for the index " + patternIndex); 1136 } 1137 divisor = longValue / (long) Math.pow(RANGE_MULTIPLIER, count - 1); 1138 } 1139 } else { 1140 // no '0's. Indicate it by negating the divisor 1141 if (divisor instanceof BigInteger) { 1142 divisor = ((BigInteger) divisor).negate(); 1143 } else { 1144 divisor = - divisor.longValue(); 1145 } 1146 } 1147 return divisor; 1148 } 1149 1150 /** 1151 * Process the series of compact patterns to compute the 1152 * series of prefixes, suffixes and their respective divisor 1153 * value. 1154 * 1155 */ 1156 private static final Pattern PLURALS = 1157 Pattern.compile("^\\{(?<plurals>.*)}$"); 1158 private static final Pattern COUNT_PATTERN = 1159 Pattern.compile("(zero|one|two|few|many|other):((' '|[^ ])+)[ ]*"); 1160 private void processCompactPatterns() { 1161 int size = compactPatterns.length; 1162 positivePrefixPatterns = new ArrayList<>(size); 1163 negativePrefixPatterns = new ArrayList<>(size); 1164 positiveSuffixPatterns = new ArrayList<>(size); 1165 negativeSuffixPatterns = new ArrayList<>(size); 1166 divisors = new ArrayList<>(size); 1167 1168 for (int index = 0; index < size; index++) { 1169 String text = compactPatterns[index]; 1170 positivePrefixPatterns.add(new Patterns()); 1171 negativePrefixPatterns.add(new Patterns()); 1172 positiveSuffixPatterns.add(new Patterns()); 1173 negativeSuffixPatterns.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 if (divisors.size() <= index) { 1424 divisors.add(computeDivisor(zeros, index)); 1425 } 1426 } else { 1427 positivePrefixPatterns.get(index).put(count, ""); 1428 negativePrefixPatterns.get(index).put(count, ""); 1429 positiveSuffixPatterns.get(index).put(count, ""); 1430 negativeSuffixPatterns.get(index).put(count, ""); 1431 if (divisors.size() <= index) { 1432 divisors.add(1L); 1433 } 1434 } 1435 } 1436 1437 private final transient DigitList digitList = new DigitList(); 1438 private static final int STATUS_INFINITE = 0; 1439 private static final int STATUS_POSITIVE = 1; 1440 private static final int STATUS_LENGTH = 2; 1441 1442 private static final char ZERO_DIGIT = '0'; 1443 private static final char DIGIT = '#'; 1444 private static final char DECIMAL_SEPARATOR = '.'; 1445 private static final char GROUPING_SEPARATOR = ','; 1446 private static final char MINUS_SIGN = '-'; 1447 private static final char PERCENT = '%'; 1448 private static final char PER_MILLE = '\u2030'; 1449 private static final char SEPARATOR = ';'; 1450 private static final char CURRENCY_SIGN = '\u00A4'; 1451 private static final char QUOTE = '\''; 1452 1453 // Expanded form of positive/negative prefix/suffix, 1454 // the expanded form contains special characters in 1455 // its localized form, which are used for matching 1456 // while parsing a string to number 1457 private transient List<Patterns> positivePrefixes; 1458 private transient List<Patterns> negativePrefixes; 1459 private transient List<Patterns> positiveSuffixes; 1460 private transient List<Patterns> negativeSuffixes; 1461 1462 private void expandAffixPatterns() { 1463 positivePrefixes = new ArrayList<>(compactPatterns.length); 1464 negativePrefixes = new ArrayList<>(compactPatterns.length); 1465 positiveSuffixes = new ArrayList<>(compactPatterns.length); 1466 negativeSuffixes = new ArrayList<>(compactPatterns.length); 1467 for (int index = 0; index < compactPatterns.length; index++) { 1468 positivePrefixes.add(positivePrefixPatterns.get(index).expandAffix()); 1469 negativePrefixes.add(negativePrefixPatterns.get(index).expandAffix()); 1470 positiveSuffixes.add(positiveSuffixPatterns.get(index).expandAffix()); 1471 negativeSuffixes.add(negativeSuffixPatterns.get(index).expandAffix()); 1472 } 1473 } 1474 1475 /** 1476 * Parses a compact number from a string to produce a {@code Number}. 1477 * <p> 1478 * The method attempts to parse text starting at the index given by 1479 * {@code pos}. 1480 * If parsing succeeds, then the index of {@code pos} is updated 1481 * to the index after the last character used (parsing does not necessarily 1482 * use all characters up to the end of the string), and the parsed 1483 * number is returned. The updated {@code pos} can be used to 1484 * indicate the starting point for the next call to this method. 1485 * If an error occurs, then the index of {@code pos} is not 1486 * changed, the error index of {@code pos} is set to the index of 1487 * the character where the error occurred, and {@code null} is returned. 1488 * <p> 1489 * The value is the numeric part in the given text multiplied 1490 * by the numeric equivalent of the affix attached 1491 * (For example, "K" = 1000 in {@link java.util.Locale#US US locale}). 1492 * The subclass returned depends on the value of 1493 * {@link #isParseBigDecimal}. 1494 * <ul> 1495 * <li>If {@link #isParseBigDecimal()} is false (the default), 1496 * most integer values are returned as {@code Long} 1497 * objects, no matter how they are written: {@code "17K"} and 1498 * {@code "17.000K"} both parse to {@code Long.valueOf(17000)}. 1499 * If the value cannot fit into {@code Long}, then the result is 1500 * returned as {@code Double}. This includes values with a 1501 * fractional part, infinite values, {@code NaN}, 1502 * and the value -0.0. 1503 * <p> 1504 * Callers may use the {@code Number} methods {@code doubleValue}, 1505 * {@code longValue}, etc., to obtain the type they want. 1506 * 1507 * <li>If {@link #isParseBigDecimal()} is true, values are returned 1508 * as {@code BigDecimal} objects. The special cases negative 1509 * and positive infinity and NaN are returned as {@code Double} 1510 * instances holding the values of the corresponding 1511 * {@code Double} constants. 1512 * </ul> 1513 * <p> 1514 * {@code CompactNumberFormat} parses all Unicode characters that represent 1515 * decimal digits, as defined by {@code Character.digit()}. In 1516 * addition, {@code CompactNumberFormat} also recognizes as digits the ten 1517 * consecutive characters starting with the localized zero digit defined in 1518 * the {@code DecimalFormatSymbols} object. 1519 * <p> 1520 * {@code CompactNumberFormat} parse does not allow parsing scientific 1521 * notations. For example, parsing a string {@code "1.05E4K"} in 1522 * {@link java.util.Locale#US US locale} breaks at character 'E' 1523 * and returns 1.05. 1524 * 1525 * @param text the string to be parsed 1526 * @param pos a {@code ParsePosition} object with index and error 1527 * index information as described above 1528 * @return the parsed value, or {@code null} if the parse fails 1529 * @throws NullPointerException if {@code text} or 1530 * {@code pos} is null 1531 * 1532 */ 1533 @Override 1534 public Number parse(String text, ParsePosition pos) { 1535 1536 Objects.requireNonNull(text); 1537 Objects.requireNonNull(pos); 1538 1539 // Lazily expanding the affix patterns, on the first parse 1540 // call on this instance 1541 // If not initialized, expand and load all affixes 1542 if (positivePrefixes == null) { 1543 expandAffixPatterns(); 1544 } 1545 1546 // The compact number multiplier for parsed string. 1547 // Its value is set on parsing prefix and suffix. For example, 1548 // in the {@link java.util.Locale#US US locale} parsing {@code "1K"} 1549 // sets its value to 1000, as K (thousand) is abbreviated form of 1000. 1550 Number cnfMultiplier = 1L; 1551 1552 // Special case NaN 1553 if (text.regionMatches(pos.index, symbols.getNaN(), 1554 0, symbols.getNaN().length())) { 1555 pos.index = pos.index + symbols.getNaN().length(); 1556 return Double.NaN; 1557 } 1558 1559 int position = pos.index; 1560 int oldStart = pos.index; 1561 boolean gotPositive = false; 1562 boolean gotNegative = false; 1563 int matchedPosIndex = -1; 1564 int matchedNegIndex = -1; 1565 String matchedPosPrefix = ""; 1566 String matchedNegPrefix = ""; 1567 String defaultPosPrefix = defaultDecimalFormat.getPositivePrefix(); 1568 String defaultNegPrefix = defaultDecimalFormat.getNegativePrefix(); 1569 double num = parseNumberPart(text, position); 1570 1571 // Prefix matching 1572 for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { 1573 String positivePrefix = getAffix(true, true, false, compactIndex, (int)num); 1574 String negativePrefix = getAffix(true, true, true, compactIndex, (int)num); 1575 1576 // Do not break if a match occur; there is a possibility that the 1577 // subsequent affixes may match the longer subsequence in the given 1578 // string. 1579 // For example, matching "Mdx 3" with "M", "Md" as prefix should 1580 // match with "Md" 1581 boolean match = matchAffix(text, position, positivePrefix, 1582 defaultPosPrefix, matchedPosPrefix); 1583 if (match) { 1584 matchedPosIndex = compactIndex; 1585 matchedPosPrefix = positivePrefix; 1586 gotPositive = true; 1587 } 1588 1589 match = matchAffix(text, position, negativePrefix, 1590 defaultNegPrefix, matchedNegPrefix); 1591 if (match) { 1592 matchedNegIndex = compactIndex; 1593 matchedNegPrefix = negativePrefix; 1594 gotNegative = true; 1595 } 1596 } 1597 1598 // Given text does not match the non empty valid compact prefixes 1599 // check with the default prefixes 1600 if (!gotPositive && !gotNegative) { 1601 if (text.regionMatches(pos.index, defaultPosPrefix, 0, 1602 defaultPosPrefix.length())) { 1603 // Matches the default positive prefix 1604 matchedPosPrefix = defaultPosPrefix; 1605 gotPositive = true; 1606 } 1607 if (text.regionMatches(pos.index, defaultNegPrefix, 0, 1608 defaultNegPrefix.length())) { 1609 // Matches the default negative prefix 1610 matchedNegPrefix = defaultNegPrefix; 1611 gotNegative = true; 1612 } 1613 } 1614 1615 // If both match, take the longest one 1616 if (gotPositive && gotNegative) { 1617 if (matchedPosPrefix.length() > matchedNegPrefix.length()) { 1618 gotNegative = false; 1619 } else if (matchedPosPrefix.length() < matchedNegPrefix.length()) { 1620 gotPositive = false; 1621 } 1622 } 1623 1624 // Update the position and take compact multiplier 1625 // only if it matches the compact prefix, not the default 1626 // prefix; else multiplier should be 1 1627 if (gotPositive) { 1628 position += matchedPosPrefix.length(); 1629 cnfMultiplier = matchedPosIndex != -1 1630 ? divisors.get(matchedPosIndex) : 1L; 1631 } else if (gotNegative) { 1632 position += matchedNegPrefix.length(); 1633 cnfMultiplier = matchedNegIndex != -1 1634 ? divisors.get(matchedNegIndex) : 1L; 1635 } 1636 1637 // If the divisor is negative, no number or suffix exists. 1638 // Return the absolute divisor value as the parse result. 1639 if (cnfMultiplier instanceof BigInteger) { 1640 BigInteger biMultiplier = (BigInteger)cnfMultiplier; 1641 if (biMultiplier.signum() == -1) { 1642 pos.index = position; 1643 return biMultiplier.negate(); 1644 } 1645 } else { 1646 if (cnfMultiplier.longValue() < 0) { 1647 pos.index = position; 1648 return Math.abs(cnfMultiplier.longValue()); 1649 } 1650 } 1651 1652 digitList.setRoundingMode(getRoundingMode()); 1653 boolean[] status = new boolean[STATUS_LENGTH]; 1654 1655 // Call DecimalFormat.subparseNumber() method to parse the 1656 // number part of the input text 1657 position = decimalFormat.subparseNumber(text, position, 1658 digitList, false, false, status); 1659 1660 if (position == -1) { 1661 // Unable to parse the number successfully 1662 pos.index = oldStart; 1663 pos.errorIndex = oldStart; 1664 return null; 1665 } 1666 1667 // If parse integer only is true and the parsing is broken at 1668 // decimal point, then pass/ignore all digits and move pointer 1669 // at the start of suffix, to process the suffix part 1670 if (isParseIntegerOnly() 1671 && text.charAt(position) == symbols.getDecimalSeparator()) { 1672 position++; // Pass decimal character 1673 for (; position < text.length(); ++position) { 1674 char ch = text.charAt(position); 1675 int digit = ch - symbols.getZeroDigit(); 1676 if (digit < 0 || digit > 9) { 1677 digit = Character.digit(ch, 10); 1678 // Parse all digit characters 1679 if (!(digit >= 0 && digit <= 9)) { 1680 break; 1681 } 1682 } 1683 } 1684 } 1685 1686 // Number parsed successfully; match prefix and 1687 // suffix to obtain multiplier 1688 pos.index = position; 1689 Number multiplier = computeParseMultiplier(text, pos, 1690 gotPositive ? matchedPosPrefix : matchedNegPrefix, 1691 status, gotPositive, gotNegative, num); 1692 1693 if (multiplier.longValue() == -1L) { 1694 return null; 1695 } else if (multiplier.longValue() != 1L) { 1696 cnfMultiplier = multiplier; 1697 } 1698 1699 // Special case INFINITY 1700 if (status[STATUS_INFINITE]) { 1701 if (status[STATUS_POSITIVE]) { 1702 return Double.POSITIVE_INFINITY; 1703 } else { 1704 return Double.NEGATIVE_INFINITY; 1705 } 1706 } 1707 1708 if (isParseBigDecimal()) { 1709 BigDecimal bigDecimalResult = digitList.getBigDecimal(); 1710 1711 if (cnfMultiplier.longValue() != 1) { 1712 bigDecimalResult = bigDecimalResult 1713 .multiply(new BigDecimal(cnfMultiplier.toString())); 1714 } 1715 if (!status[STATUS_POSITIVE]) { 1716 bigDecimalResult = bigDecimalResult.negate(); 1717 } 1718 return bigDecimalResult; 1719 } else { 1720 Number cnfResult; 1721 if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { 1722 long longResult = digitList.getLong(); 1723 cnfResult = generateParseResult(longResult, false, 1724 longResult < 0, status, cnfMultiplier); 1725 } else { 1726 cnfResult = generateParseResult(digitList.getDouble(), 1727 true, false, status, cnfMultiplier); 1728 } 1729 return cnfResult; 1730 } 1731 } 1732 1733 private static final Pattern DIGITS = Pattern.compile("\\p{Nd}+"); 1734 /** 1735 * Parse the number part in the input text into a number 1736 * 1737 * @param text input text to be parsed 1738 * @param position starting position 1739 * @return the number 1740 */ 1741 private double parseNumberPart(String text, int position) { 1742 if (text.startsWith(symbols.getInfinity(), position)) { 1743 return Double.POSITIVE_INFINITY; 1744 } else if (!text.startsWith(symbols.getNaN(), position)) { 1745 Matcher m = DIGITS.matcher(text); 1746 if (m.find(position)) { 1747 String digits = m.group(); 1748 int cp = digits.codePointAt(0); 1749 if (Character.isDigit(cp)) { 1750 return Double.parseDouble(digits.codePoints() 1751 .map(Character::getNumericValue) 1752 .mapToObj(Integer::toString) 1753 .collect(Collectors.joining())); 1754 } 1755 } 1756 } 1757 return Double.NaN; 1758 } 1759 1760 /** 1761 * Returns the parsed result by multiplying the parsed number 1762 * with the multiplier representing the prefix and suffix. 1763 * 1764 * @param number parsed number component 1765 * @param gotDouble whether the parsed number contains decimal 1766 * @param gotLongMin whether the parsed number is Long.MIN 1767 * @param status boolean status flags indicating whether the 1768 * value is infinite and whether it is positive 1769 * @param cnfMultiplier compact number multiplier 1770 * @return parsed result 1771 */ 1772 private Number generateParseResult(Number number, boolean gotDouble, 1773 boolean gotLongMin, boolean[] status, Number cnfMultiplier) { 1774 1775 if (gotDouble) { 1776 if (cnfMultiplier.longValue() != 1L) { 1777 double doubleResult = number.doubleValue() * cnfMultiplier.doubleValue(); 1778 doubleResult = (double) convertIfNegative(doubleResult, status, gotLongMin); 1779 // Check if a double can be represeneted as a long 1780 long longResult = (long) doubleResult; 1781 gotDouble = ((doubleResult != (double) longResult) 1782 || (doubleResult == 0.0 && 1 / doubleResult < 0.0)); 1783 return gotDouble ? (Number) doubleResult : (Number) longResult; 1784 } 1785 } else { 1786 if (cnfMultiplier.longValue() != 1L) { 1787 Number result; 1788 if ((cnfMultiplier instanceof Long) && !gotLongMin) { 1789 long longMultiplier = (long) cnfMultiplier; 1790 try { 1791 result = Math.multiplyExact(number.longValue(), 1792 longMultiplier); 1793 } catch (ArithmeticException ex) { 1794 // If number * longMultiplier can not be represented 1795 // as long return as double 1796 result = number.doubleValue() * cnfMultiplier.doubleValue(); 1797 } 1798 } else { 1799 // cnfMultiplier can not be stored into long or the number 1800 // part is Long.MIN, return as double 1801 result = number.doubleValue() * cnfMultiplier.doubleValue(); 1802 } 1803 return convertIfNegative(result, status, gotLongMin); 1804 } 1805 } 1806 1807 // Default number 1808 return convertIfNegative(number, status, gotLongMin); 1809 } 1810 1811 /** 1812 * Negate the parsed value if the positive status flag is false 1813 * and the value is not a Long.MIN 1814 * @param number parsed value 1815 * @param status boolean status flags indicating whether the 1816 * value is infinite and whether it is positive 1817 * @param gotLongMin whether the parsed number is Long.MIN 1818 * @return the resulting value 1819 */ 1820 private Number convertIfNegative(Number number, boolean[] status, 1821 boolean gotLongMin) { 1822 1823 if (!status[STATUS_POSITIVE] && !gotLongMin) { 1824 if (number instanceof Long) { 1825 return -(long) number; 1826 } else { 1827 return -(double) number; 1828 } 1829 } else { 1830 return number; 1831 } 1832 } 1833 1834 /** 1835 * Attempts to match the given {@code affix} in the 1836 * specified {@code text}. 1837 */ 1838 private boolean matchAffix(String text, int position, String affix, 1839 String defaultAffix, String matchedAffix) { 1840 1841 // Check with the compact affixes which are non empty and 1842 // do not match with default affix 1843 if (!affix.isEmpty() && !affix.equals(defaultAffix)) { 1844 // Look ahead only for the longer match than the previous match 1845 if (matchedAffix.length() < affix.length()) { 1846 return text.regionMatches(position, affix, 0, affix.length()); 1847 } 1848 } 1849 return false; 1850 } 1851 1852 /** 1853 * Attempts to match given {@code prefix} and {@code suffix} in 1854 * the specified {@code text}. 1855 */ 1856 private boolean matchPrefixAndSuffix(String text, int position, String prefix, 1857 String matchedPrefix, String defaultPrefix, String suffix, 1858 String matchedSuffix, String defaultSuffix) { 1859 1860 // Check the compact pattern suffix only if there is a 1861 // compact prefix match or a default prefix match 1862 // because the compact prefix and suffix should match at the same 1863 // index to obtain the multiplier. 1864 // The prefix match is required because of the possibility of 1865 // same prefix at multiple index, in which case matching the suffix 1866 // is used to obtain the single match 1867 1868 if (prefix.equals(matchedPrefix) 1869 || matchedPrefix.equals(defaultPrefix)) { 1870 return matchAffix(text, position, suffix, defaultSuffix, matchedSuffix); 1871 } 1872 return false; 1873 } 1874 1875 /** 1876 * Computes multiplier by matching the given {@code matchedPrefix} 1877 * and suffix in the specified {@code text} from the lists of 1878 * prefixes and suffixes extracted from compact patterns. 1879 * 1880 * @param text the string to parse 1881 * @param parsePosition the {@code ParsePosition} object representing the 1882 * index and error index of the parse string 1883 * @param matchedPrefix prefix extracted which needs to be matched to 1884 * obtain the multiplier 1885 * @param status upon return contains boolean status flags indicating 1886 * whether the value is positive 1887 * @param gotPositive based on the prefix parsed; whether the number is positive 1888 * @param gotNegative based on the prefix parsed; whether the number is negative 1889 * @return the multiplier matching the prefix and suffix; -1 otherwise 1890 */ 1891 private Number computeParseMultiplier(String text, ParsePosition parsePosition, 1892 String matchedPrefix, boolean[] status, boolean gotPositive, 1893 boolean gotNegative, double num) { 1894 1895 int position = parsePosition.index; 1896 boolean gotPos = false; 1897 boolean gotNeg = false; 1898 int matchedPosIndex = -1; 1899 int matchedNegIndex = -1; 1900 String matchedPosSuffix = ""; 1901 String matchedNegSuffix = ""; 1902 for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { 1903 String positivePrefix = getAffix(true, true, false, compactIndex, (int)num); 1904 String negativePrefix = getAffix(true, true, true, compactIndex, (int)num); 1905 String positiveSuffix = getAffix(true, false, false, compactIndex, (int)num); 1906 String negativeSuffix = getAffix(true, false, true, compactIndex, (int)num); 1907 1908 // Do not break if a match occur; there is a possibility that the 1909 // subsequent affixes may match the longer subsequence in the given 1910 // string. 1911 // For example, matching "3Mdx" with "M", "Md" should match with "Md" 1912 boolean match = matchPrefixAndSuffix(text, position, positivePrefix, matchedPrefix, 1913 defaultDecimalFormat.getPositivePrefix(), positiveSuffix, 1914 matchedPosSuffix, defaultDecimalFormat.getPositiveSuffix()); 1915 if (match) { 1916 matchedPosIndex = compactIndex; 1917 matchedPosSuffix = positiveSuffix; 1918 gotPos = true; 1919 } 1920 1921 match = matchPrefixAndSuffix(text, position, negativePrefix, matchedPrefix, 1922 defaultDecimalFormat.getNegativePrefix(), negativeSuffix, 1923 matchedNegSuffix, defaultDecimalFormat.getNegativeSuffix()); 1924 if (match) { 1925 matchedNegIndex = compactIndex; 1926 matchedNegSuffix = negativeSuffix; 1927 gotNeg = true; 1928 } 1929 } 1930 1931 // Suffix in the given text does not match with the compact 1932 // patterns suffixes; match with the default suffix 1933 if (!gotPos && !gotNeg) { 1934 String positiveSuffix = defaultDecimalFormat.getPositiveSuffix(); 1935 String negativeSuffix = defaultDecimalFormat.getNegativeSuffix(); 1936 if (text.regionMatches(position, positiveSuffix, 0, 1937 positiveSuffix.length())) { 1938 // Matches the default positive prefix 1939 matchedPosSuffix = positiveSuffix; 1940 gotPos = true; 1941 } 1942 if (text.regionMatches(position, negativeSuffix, 0, 1943 negativeSuffix.length())) { 1944 // Matches the default negative suffix 1945 matchedNegSuffix = negativeSuffix; 1946 gotNeg = true; 1947 } 1948 } 1949 1950 // If both matches, take the longest one 1951 if (gotPos && gotNeg) { 1952 if (matchedPosSuffix.length() > matchedNegSuffix.length()) { 1953 gotNeg = false; 1954 } else if (matchedPosSuffix.length() < matchedNegSuffix.length()) { 1955 gotPos = false; 1956 } else { 1957 // If longest comparison fails; take the positive and negative 1958 // sign of matching prefix 1959 gotPos = gotPositive; 1960 gotNeg = gotNegative; 1961 } 1962 } 1963 1964 // Fail if neither or both 1965 if (gotPos == gotNeg) { 1966 parsePosition.errorIndex = position; 1967 return -1L; 1968 } 1969 1970 Number cnfMultiplier; 1971 // Update the parse position index and take compact multiplier 1972 // only if it matches the compact suffix, not the default 1973 // suffix; else multiplier should be 1 1974 if (gotPos) { 1975 parsePosition.index = position + matchedPosSuffix.length(); 1976 cnfMultiplier = matchedPosIndex != -1 1977 ? divisors.get(matchedPosIndex) : 1L; 1978 } else { 1979 parsePosition.index = position + matchedNegSuffix.length(); 1980 cnfMultiplier = matchedNegIndex != -1 1981 ? divisors.get(matchedNegIndex) : 1L; 1982 } 1983 status[STATUS_POSITIVE] = gotPos; 1984 return cnfMultiplier; 1985 } 1986 1987 /** 1988 * Reconstitutes this {@code CompactNumberFormat} from a stream 1989 * (that is, deserializes it) after performing some validations. 1990 * This method throws InvalidObjectException, if the stream data is invalid 1991 * because of the following reasons, 1992 * <ul> 1993 * <li> If any of the {@code decimalPattern}, {@code compactPatterns}, 1994 * {@code symbols} or {@code roundingMode} is {@code null}. 1995 * <li> If the {@code decimalPattern} or the {@code compactPatterns} array 1996 * contains an invalid pattern or if a {@code null} appears in the array of 1997 * compact patterns. 1998 * <li> If the {@code minimumIntegerDigits} is greater than the 1999 * {@code maximumIntegerDigits} or the {@code minimumFractionDigits} is 2000 * greater than the {@code maximumFractionDigits}. This check is performed 2001 * by superclass's Object. 2002 * <li> If any of the minimum/maximum integer/fraction digit count is 2003 * negative. This check is performed by superclass's readObject. 2004 * <li> If the minimum or maximum integer digit count is larger than 309 or 2005 * if the minimum or maximum fraction digit count is larger than 340. 2006 * <li> If the grouping size is negative or larger than 127. 2007 * </ul> 2008 * If the {@code pluralRules} field is not deserialized from the stream, it 2009 * will be set to an empty string. 2010 * 2011 * @param inStream the stream 2012 * @throws IOException if an I/O error occurs 2013 * @throws ClassNotFoundException if the class of a serialized object 2014 * could not be found 2015 */ 2016 @java.io.Serial 2017 private void readObject(ObjectInputStream inStream) throws IOException, 2018 ClassNotFoundException { 2019 2020 inStream.defaultReadObject(); 2021 if (decimalPattern == null || compactPatterns == null 2022 || symbols == null || roundingMode == null) { 2023 throw new InvalidObjectException("One of the 'decimalPattern'," 2024 + " 'compactPatterns', 'symbols' or 'roundingMode'" 2025 + " is null"); 2026 } 2027 2028 // Check only the maximum counts because NumberFormat.readObject has 2029 // already ensured that the maximum is greater than the minimum count. 2030 if (getMaximumIntegerDigits() > DecimalFormat.DOUBLE_INTEGER_DIGITS 2031 || getMaximumFractionDigits() > DecimalFormat.DOUBLE_FRACTION_DIGITS) { 2032 throw new InvalidObjectException("Digit count out of range"); 2033 } 2034 2035 // Check if the grouping size is negative, on an attempt to 2036 // put value > 127, it wraps around, so check just negative value 2037 if (groupingSize < 0) { 2038 throw new InvalidObjectException("Grouping size is negative"); 2039 } 2040 2041 // pluralRules is since 14. Fill in empty string if it is null 2042 if (pluralRules == null) { 2043 pluralRules = ""; 2044 } 2045 2046 try { 2047 processCompactPatterns(); 2048 } catch (IllegalArgumentException ex) { 2049 throw new InvalidObjectException(ex.getMessage()); 2050 } 2051 2052 decimalFormat = new DecimalFormat(SPECIAL_PATTERN, symbols); 2053 decimalFormat.setMaximumFractionDigits(getMaximumFractionDigits()); 2054 decimalFormat.setMinimumFractionDigits(getMinimumFractionDigits()); 2055 decimalFormat.setMaximumIntegerDigits(getMaximumIntegerDigits()); 2056 decimalFormat.setMinimumIntegerDigits(getMinimumIntegerDigits()); 2057 decimalFormat.setRoundingMode(getRoundingMode()); 2058 decimalFormat.setGroupingSize(getGroupingSize()); 2059 decimalFormat.setGroupingUsed(isGroupingUsed()); 2060 decimalFormat.setParseIntegerOnly(isParseIntegerOnly()); 2061 2062 try { 2063 defaultDecimalFormat = new DecimalFormat(decimalPattern, symbols); 2064 defaultDecimalFormat.setMaximumFractionDigits(0); 2065 } catch (IllegalArgumentException ex) { 2066 throw new InvalidObjectException(ex.getMessage()); 2067 } 2068 2069 } 2070 2071 /** 2072 * Sets the maximum number of digits allowed in the integer portion of a 2073 * number. 2074 * The maximum allowed integer range is 309, if the {@code newValue} > 309, 2075 * then the maximum integer digits count is set to 309. Negative input 2076 * values are replaced with 0. 2077 * 2078 * @param newValue the maximum number of integer digits to be shown 2079 * @see #getMaximumIntegerDigits() 2080 */ 2081 @Override 2082 public void setMaximumIntegerDigits(int newValue) { 2083 // The maximum integer digits is checked with the allowed range before calling 2084 // the DecimalFormat.setMaximumIntegerDigits, which performs the negative check 2085 // on the given newValue while setting it as max integer digits. 2086 // For example, if a negative value is specified, it is replaced with 0 2087 decimalFormat.setMaximumIntegerDigits(Math.min(newValue, 2088 DecimalFormat.DOUBLE_INTEGER_DIGITS)); 2089 super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 2090 if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) { 2091 decimalFormat.setMinimumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 2092 super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 2093 } 2094 } 2095 2096 /** 2097 * Sets the minimum number of digits allowed in the integer portion of a 2098 * number. 2099 * The maximum allowed integer range is 309, if the {@code newValue} > 309, 2100 * then the minimum integer digits count is set to 309. Negative input 2101 * values are replaced with 0. 2102 * 2103 * @param newValue the minimum number of integer digits to be shown 2104 * @see #getMinimumIntegerDigits() 2105 */ 2106 @Override 2107 public void setMinimumIntegerDigits(int newValue) { 2108 // The minimum integer digits is checked with the allowed range before calling 2109 // the DecimalFormat.setMinimumIntegerDigits, which performs check on the given 2110 // newValue while setting it as min integer digits. For example, if a negative 2111 // value is specified, it is replaced with 0 2112 decimalFormat.setMinimumIntegerDigits(Math.min(newValue, 2113 DecimalFormat.DOUBLE_INTEGER_DIGITS)); 2114 super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 2115 if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) { 2116 decimalFormat.setMaximumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); 2117 super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); 2118 } 2119 } 2120 2121 /** 2122 * Sets the minimum number of digits allowed in the fraction portion of a 2123 * number. 2124 * The maximum allowed fraction range is 340, if the {@code newValue} > 340, 2125 * then the minimum fraction digits count is set to 340. Negative input 2126 * values are replaced with 0. 2127 * 2128 * @param newValue the minimum number of fraction digits to be shown 2129 * @see #getMinimumFractionDigits() 2130 */ 2131 @Override 2132 public void setMinimumFractionDigits(int newValue) { 2133 // The minimum fraction digits is checked with the allowed range before 2134 // calling the DecimalFormat.setMinimumFractionDigits, which performs 2135 // check on the given newValue while setting it as min fraction 2136 // digits. For example, if a negative value is specified, it is 2137 // replaced with 0 2138 decimalFormat.setMinimumFractionDigits(Math.min(newValue, 2139 DecimalFormat.DOUBLE_FRACTION_DIGITS)); 2140 super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); 2141 if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) { 2142 decimalFormat.setMaximumFractionDigits(decimalFormat.getMinimumFractionDigits()); 2143 super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); 2144 } 2145 } 2146 2147 /** 2148 * Sets the maximum number of digits allowed in the fraction portion of a 2149 * number. 2150 * The maximum allowed fraction range is 340, if the {@code newValue} > 340, 2151 * then the maximum fraction digits count is set to 340. Negative input 2152 * values are replaced with 0. 2153 * 2154 * @param newValue the maximum number of fraction digits to be shown 2155 * @see #getMaximumFractionDigits() 2156 */ 2157 @Override 2158 public void setMaximumFractionDigits(int newValue) { 2159 // The maximum fraction digits is checked with the allowed range before 2160 // calling the DecimalFormat.setMaximumFractionDigits, which performs 2161 // check on the given newValue while setting it as max fraction digits. 2162 // For example, if a negative value is specified, it is replaced with 0 2163 decimalFormat.setMaximumFractionDigits(Math.min(newValue, 2164 DecimalFormat.DOUBLE_FRACTION_DIGITS)); 2165 super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); 2166 if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) { 2167 decimalFormat.setMinimumFractionDigits(decimalFormat.getMaximumFractionDigits()); 2168 super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); 2169 } 2170 } 2171 2172 /** 2173 * Gets the {@link java.math.RoundingMode} used in this 2174 * {@code CompactNumberFormat}. 2175 * 2176 * @return the {@code RoundingMode} used for this 2177 * {@code CompactNumberFormat} 2178 * @see #setRoundingMode(RoundingMode) 2179 */ 2180 @Override 2181 public RoundingMode getRoundingMode() { 2182 return roundingMode; 2183 } 2184 2185 /** 2186 * Sets the {@link java.math.RoundingMode} used in this 2187 * {@code CompactNumberFormat}. 2188 * 2189 * @param roundingMode the {@code RoundingMode} to be used 2190 * @see #getRoundingMode() 2191 * @throws NullPointerException if {@code roundingMode} is {@code null} 2192 */ 2193 @Override 2194 public void setRoundingMode(RoundingMode roundingMode) { 2195 decimalFormat.setRoundingMode(roundingMode); 2196 this.roundingMode = roundingMode; 2197 } 2198 2199 /** 2200 * Returns the grouping size. Grouping size is the number of digits between 2201 * grouping separators in the integer portion of a number. For example, 2202 * in the compact number {@code "12,347 trillion"} for the 2203 * {@link java.util.Locale#US US locale}, the grouping size is 3. 2204 * 2205 * @return the grouping size 2206 * @see #setGroupingSize 2207 * @see java.text.NumberFormat#isGroupingUsed 2208 * @see java.text.DecimalFormatSymbols#getGroupingSeparator 2209 */ 2210 public int getGroupingSize() { 2211 return groupingSize; 2212 } 2213 2214 /** 2215 * Sets the grouping size. Grouping size is the number of digits between 2216 * grouping separators in the integer portion of a number. For example, 2217 * in the compact number {@code "12,347 trillion"} for the 2218 * {@link java.util.Locale#US US locale}, the grouping size is 3. The grouping 2219 * size must be greater than or equal to zero and less than or equal to 127. 2220 * 2221 * @param newValue the new grouping size 2222 * @see #getGroupingSize 2223 * @see java.text.NumberFormat#setGroupingUsed 2224 * @see java.text.DecimalFormatSymbols#setGroupingSeparator 2225 * @throws IllegalArgumentException if {@code newValue} is negative or 2226 * larger than 127 2227 */ 2228 public void setGroupingSize(int newValue) { 2229 if (newValue < 0 || newValue > 127) { 2230 throw new IllegalArgumentException( 2231 "The value passed is negative or larger than 127"); 2232 } 2233 groupingSize = (byte) newValue; 2234 decimalFormat.setGroupingSize(groupingSize); 2235 } 2236 2237 /** 2238 * Returns true if grouping is used in this format. For example, with 2239 * grouping on and grouping size set to 3, the number {@code 12346567890987654} 2240 * can be formatted as {@code "12,347 trillion"} in the 2241 * {@link java.util.Locale#US US locale}. 2242 * The grouping separator is locale dependent. 2243 * 2244 * @return {@code true} if grouping is used; 2245 * {@code false} otherwise 2246 * @see #setGroupingUsed 2247 */ 2248 @Override 2249 public boolean isGroupingUsed() { 2250 return super.isGroupingUsed(); 2251 } 2252 2253 /** 2254 * Sets whether or not grouping will be used in this format. 2255 * 2256 * @param newValue {@code true} if grouping is used; 2257 * {@code false} otherwise 2258 * @see #isGroupingUsed 2259 */ 2260 @Override 2261 public void setGroupingUsed(boolean newValue) { 2262 decimalFormat.setGroupingUsed(newValue); 2263 super.setGroupingUsed(newValue); 2264 } 2265 2266 /** 2267 * Returns true if this format parses only an integer from the number 2268 * component of a compact number. 2269 * Parsing an integer means that only an integer is considered from the 2270 * number component, prefix/suffix is still considered to compute the 2271 * resulting output. 2272 * For example, in the {@link java.util.Locale#US US locale}, if this method 2273 * returns {@code true}, the string {@code "1234.78 thousand"} would be 2274 * parsed as the value {@code 1234000} (1234 (integer part) * 1000 2275 * (thousand)) and the fractional part would be skipped. 2276 * The exact format accepted by the parse operation is locale dependent. 2277 * 2278 * @return {@code true} if compact numbers should be parsed as integers 2279 * only; {@code false} otherwise 2280 */ 2281 @Override 2282 public boolean isParseIntegerOnly() { 2283 return super.isParseIntegerOnly(); 2284 } 2285 2286 /** 2287 * Sets whether or not this format parses only an integer from the number 2288 * component of a compact number. 2289 * 2290 * @param value {@code true} if compact numbers should be parsed as 2291 * integers only; {@code false} otherwise 2292 * @see #isParseIntegerOnly 2293 */ 2294 @Override 2295 public void setParseIntegerOnly(boolean value) { 2296 decimalFormat.setParseIntegerOnly(value); 2297 super.setParseIntegerOnly(value); 2298 } 2299 2300 /** 2301 * Returns whether the {@link #parse(String, ParsePosition)} 2302 * method returns {@code BigDecimal}. The default value is false. 2303 * 2304 * @return {@code true} if the parse method returns BigDecimal; 2305 * {@code false} otherwise 2306 * @see #setParseBigDecimal 2307 * 2308 */ 2309 public boolean isParseBigDecimal() { 2310 return parseBigDecimal; 2311 } 2312 2313 /** 2314 * Sets whether the {@link #parse(String, ParsePosition)} 2315 * method returns {@code BigDecimal}. 2316 * 2317 * @param newValue {@code true} if the parse method returns BigDecimal; 2318 * {@code false} otherwise 2319 * @see #isParseBigDecimal 2320 * 2321 */ 2322 public void setParseBigDecimal(boolean newValue) { 2323 parseBigDecimal = newValue; 2324 } 2325 2326 /** 2327 * Checks if this {@code CompactNumberFormat} is equal to the 2328 * specified {@code obj}. The objects of type {@code CompactNumberFormat} 2329 * are compared, other types return false; obeys the general contract of 2330 * {@link java.lang.Object#equals(java.lang.Object) Object.equals}. 2331 * 2332 * @param obj the object to compare with 2333 * @return true if this is equal to the other {@code CompactNumberFormat} 2334 */ 2335 @Override 2336 public boolean equals(Object obj) { 2337 2338 if (!super.equals(obj)) { 2339 return false; 2340 } 2341 2342 CompactNumberFormat other = (CompactNumberFormat) obj; 2343 return decimalPattern.equals(other.decimalPattern) 2344 && symbols.equals(other.symbols) 2345 && Arrays.equals(compactPatterns, other.compactPatterns) 2346 && roundingMode.equals(other.roundingMode) 2347 && pluralRules.equals(other.pluralRules) 2348 && groupingSize == other.groupingSize 2349 && parseBigDecimal == other.parseBigDecimal; 2350 } 2351 2352 /** 2353 * Returns the hash code for this {@code CompactNumberFormat} instance. 2354 * 2355 * @return hash code for this {@code CompactNumberFormat} 2356 */ 2357 @Override 2358 public int hashCode() { 2359 return 31 * super.hashCode() + 2360 Objects.hash(decimalPattern, symbols, roundingMode, pluralRules) 2361 + Arrays.hashCode(compactPatterns) + groupingSize 2362 + Boolean.hashCode(parseBigDecimal); 2363 } 2364 2365 /** 2366 * Creates and returns a copy of this {@code CompactNumberFormat} 2367 * instance. 2368 * 2369 * @return a clone of this instance 2370 */ 2371 @Override 2372 public CompactNumberFormat clone() { 2373 CompactNumberFormat other = (CompactNumberFormat) super.clone(); 2374 other.compactPatterns = compactPatterns.clone(); 2375 other.symbols = (DecimalFormatSymbols) symbols.clone(); 2376 return other; 2377 } 2378 2379 /** 2380 * Abstraction of affix patterns for each "count" tag. 2381 */ 2382 private final class Patterns { 2383 private final Map<String, String> patternsMap = new HashMap<>(); 2384 2385 void put(String count, String pattern) { 2386 patternsMap.put(count, pattern); 2387 } 2388 2389 String get(double num) { 2390 return patternsMap.getOrDefault(getPluralCategory(num), 2391 patternsMap.getOrDefault("other", "")); 2392 } 2393 2394 Patterns expandAffix() { 2395 Patterns ret = new Patterns(); 2396 patternsMap.forEach((key, value) -> ret.put(key, CompactNumberFormat.this.expandAffix(value))); 2397 return ret; 2398 } 2399 } 2400 2401 private int getIntegerPart(double number, double divisor) { 2402 return BigDecimal.valueOf(number) 2403 .divide(BigDecimal.valueOf(divisor), roundingMode).intValue(); 2404 } 2405 2406 /** 2407 * Returns LDML's tag from the plurals rules 2408 * 2409 * @param input input number in double type 2410 * @return LDML "count" tag 2411 */ 2412 private String getPluralCategory(double input) { 2413 if (rulesMap != null) { 2414 return rulesMap.entrySet().stream() 2415 .filter(e -> matchPluralRule(e.getValue(), input)) 2416 .map(Map.Entry::getKey) 2417 .findFirst() 2418 .orElse("other"); 2419 } 2420 2421 // defaults to "other" 2422 return "other"; 2423 } 2424 2425 private static boolean matchPluralRule(String condition, double input) { 2426 return Arrays.stream(condition.split("or")) 2427 .anyMatch(and_condition -> Arrays.stream(and_condition.split("and")) 2428 .allMatch(r -> relationCheck(r, input))); 2429 } 2430 2431 private final static String NAMED_EXPR = "(?<op>[niftvw])\\s*((?<div>[/%])\\s*(?<val>\\d+))*"; 2432 private final static String NAMED_RELATION = "(?<rel>!?=)"; 2433 private final static String NAMED_VALUE_RANGE = "(?<start>\\d+)\\.\\.(?<end>\\d+)|(?<value>\\d+)"; 2434 private final static Pattern EXPR_PATTERN = Pattern.compile(NAMED_EXPR); 2435 private final static Pattern RELATION_PATTERN = Pattern.compile(NAMED_RELATION); 2436 private final static Pattern VALUE_RANGE_PATTERN = Pattern.compile(NAMED_VALUE_RANGE); 2437 2438 /** 2439 * Checks if the 'input' equals the value, or within the range. 2440 * 2441 * @param valueOrRange A string representing either a single value or a range 2442 * @param input to examine in double 2443 * @return match indicator 2444 */ 2445 private static boolean valOrRangeMatches(String valueOrRange, double input) { 2446 Matcher m = VALUE_RANGE_PATTERN.matcher(valueOrRange); 2447 2448 if (m.find()) { 2449 String value = m.group("value"); 2450 if (value != null) { 2451 return input == Double.parseDouble(value); 2452 } else { 2453 return input >= Double.parseDouble(m.group("start")) && 2454 input <= Double.parseDouble(m.group("end")); 2455 } 2456 } 2457 2458 return false; 2459 } 2460 2461 /** 2462 * Checks if the input value satisfies the relation. Each possible value or range is 2463 * separated by a comma ',' 2464 * 2465 * @param relation relation string, e.g, "n = 1, 3..5", or "n != 1, 3..5" 2466 * @param input value to examine in double 2467 * @return boolean to indicate whether the relation satisfies or not. If the relation 2468 * is '=', true if any of the possible value/range satisfies. If the relation is '!=', 2469 * none of the possible value/range should satisfy to return true. 2470 */ 2471 private static boolean relationCheck(String relation, double input) { 2472 Matcher expr = EXPR_PATTERN.matcher(relation); 2473 2474 if (expr.find()) { 2475 double lop = evalLOperand(expr, input); 2476 Matcher rel = RELATION_PATTERN.matcher(relation); 2477 2478 if (rel.find(expr.end())) { 2479 var conditions = 2480 Arrays.stream(relation.substring(rel.end()).split(",")); 2481 2482 if (Objects.equals(rel.group("rel"), "!=")) { 2483 return conditions.noneMatch(c -> valOrRangeMatches(c, lop)); 2484 } else { 2485 return conditions.anyMatch(c -> valOrRangeMatches(c, lop)); 2486 } 2487 } 2488 } 2489 2490 return false; 2491 } 2492 2493 /** 2494 * Evaluates the left operand value. 2495 * 2496 * @param expr Match result 2497 * @param input value to examine in double 2498 * @return resulting double value 2499 */ 2500 private static double evalLOperand(Matcher expr, double input) { 2501 double ret = 0; 2502 2503 if (input == Double.POSITIVE_INFINITY) { 2504 ret =input; 2505 } else { 2506 String op = expr.group("op"); 2507 if (Objects.equals(op, "n") || Objects.equals(op, "i")) { 2508 ret = input; 2509 } 2510 2511 String divop = expr.group("div"); 2512 if (divop != null) { 2513 String divisor = expr.group("val"); 2514 switch (divop) { 2515 case "%" -> ret %= Double.parseDouble(divisor); 2516 case "/" -> ret /= Double.parseDouble(divisor); 2517 } 2518 } 2519 } 2520 2521 return ret; 2522 } 2523 }