< prev index next >

src/java.base/share/classes/java/text/CompactNumberFormat.java

Print this page
rev 60563 : [mq]: 8251499


  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 import java.util.stream.Stream;
  46 
  47 
  48 /**
  49  * <p>
  50  * {@code CompactNumberFormat} is a concrete subclass of {@code NumberFormat}
  51  * that formats a decimal number in its compact form.
  52  *
  53  * The compact number formatting is designed for the environment where the space
  54  * is limited, and the formatted string can be displayed in that limited space.
  55  * It is defined by LDML's specification for
  56  * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats">
  57  * Compact Number Formats</a>. A compact number formatting refers
  58  * to the representation of a number in a shorter form, based on the patterns
  59  * provided for a given locale.
  60  *
  61  * <p>
  62  * For example:
  63  * <br>In the {@link java.util.Locale#US US locale}, {@code 1000} can be formatted
  64  * as {@code "1K"}, and {@code 1000000} as {@code "1M"}, depending upon the
  65  * <a href = "#compact_number_style" >style</a> used.


 572         // part of the rounded value is used for matching the compact
 573         // number pattern
 574         // For example, if roundingMode is HALF_UP with min fraction
 575         // digits = 0, the number 999.6 should round up
 576         // to 1000 and outputs 1K/thousand in "en_US" locale
 577         DigitList dList = new DigitList();
 578         dList.setRoundingMode(getRoundingMode());
 579         number = isNegative ? -number : number;
 580         dList.set(isNegative, number, getMinimumFractionDigits());
 581 
 582         double roundedNumber = dList.getDouble();
 583         int compactDataIndex = selectCompactPattern((long) roundedNumber);
 584         if (compactDataIndex != -1) {
 585             long divisor = (Long) divisors.get(compactDataIndex);
 586             int iPart = getIntegerPart(number, divisor);
 587             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
 588             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
 589 
 590             if (!prefix.isEmpty() || !suffix.isEmpty()) {
 591                 appendPrefix(result, prefix, delegate);

 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             } else {
 599                 defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative);
 600             }
 601         } else {
 602             defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative);
 603         }
 604         return result;
 605     }
 606 
 607     /**
 608      * Formats a long to produce a string representing its compact form.
 609      * @param number    the long number to format
 610      * @param result    where the text is to be appended
 611      * @param fieldPosition    keeps track on the position of the field within
 612      *                         the returned string. For example, to format
 613      *                         a number {@code 123456789} in the
 614      *                         {@link java.util.Locale#US US locale},
 615      *                         if the given {@code fieldPosition} is
 616      *                         {@link NumberFormat#INTEGER_FIELD}, the begin
 617      *                         index and end index of {@code fieldPosition}


 638 
 639     private StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) {
 640         boolean isNegative = (number < 0);
 641         if (isNegative) {
 642             number = -number;
 643         }
 644 
 645         if (number < 0) { // LONG_MIN
 646             BigInteger bigIntegerValue = BigInteger.valueOf(number);
 647             return format(bigIntegerValue, result, delegate, true);
 648         }
 649 
 650         int compactDataIndex = selectCompactPattern(number);
 651         if (compactDataIndex != -1) {
 652             long divisor = (Long) divisors.get(compactDataIndex);
 653             int iPart = getIntegerPart(number, divisor);
 654             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
 655             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
 656             if (!prefix.isEmpty() || !suffix.isEmpty()) {
 657                 appendPrefix(result, prefix, delegate);

 658                 if ((number % divisor == 0)) {
 659                     number = number / divisor;
 660                     decimalFormat.setDigitList(number, isNegative, 0);
 661                     decimalFormat.subformatNumber(result, delegate,
 662                             isNegative, true, getMaximumIntegerDigits(),
 663                             getMinimumIntegerDigits(), getMaximumFractionDigits(),
 664                             getMinimumFractionDigits());
 665                 } else {
 666                     // To avoid truncation of fractional part store
 667                     // the value in double and follow double path instead of
 668                     // long path
 669                     double dNumber = (double) number / divisor;
 670                     decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits());
 671                     decimalFormat.subformatNumber(result, delegate,
 672                             isNegative, false, getMaximumIntegerDigits(),
 673                             getMinimumIntegerDigits(), getMaximumFractionDigits(),
 674                             getMinimumFractionDigits());
 675                 }
 676                 appendSuffix(result, suffix, delegate);

 677             } else {
 678                 number = isNegative ? -number : number;
 679                 defaultDecimalFormat.format(number, result, delegate);
 680             }
 681         } else {
 682             number = isNegative ? -number : number;
 683             defaultDecimalFormat.format(number, result, delegate);
 684         }
 685         return result;
 686     }
 687 
 688     /**
 689      * Formats a BigDecimal to produce a string representing its compact form.
 690      * @param number    the BigDecimal number to format
 691      * @param result    where the text is to be appended
 692      * @param fieldPosition    keeps track on the position of the field within
 693      *                         the returned string. For example, to format
 694      *                         a number {@code 1234567.89} in the
 695      *                         {@link java.util.Locale#US US locale},
 696      *                         if the given {@code fieldPosition} is


 731         // For example, If roundingMode is HALF_UP with min fraction digits = 0,
 732         // the number 999.6 should round up
 733         // to 1000 and outputs 1K/thousand in "en_US" locale
 734         number = number.setScale(getMinimumFractionDigits(), getRoundingMode());
 735 
 736         int compactDataIndex;
 737         if (number.toBigInteger().bitLength() < 64) {
 738             long longNumber = number.toBigInteger().longValue();
 739             compactDataIndex = selectCompactPattern(longNumber);
 740         } else {
 741             compactDataIndex = selectCompactPattern(number.toBigInteger());
 742         }
 743 
 744         if (compactDataIndex != -1) {
 745             Number divisor = divisors.get(compactDataIndex);
 746             int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue());
 747             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
 748             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
 749             if (!prefix.isEmpty() || !suffix.isEmpty()) {
 750                 appendPrefix(result, prefix, delegate);

 751                 number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode());
 752                 decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits());
 753                 decimalFormat.subformatNumber(result, delegate, isNegative,
 754                         false, getMaximumIntegerDigits(), getMinimumIntegerDigits(),
 755                         getMaximumFractionDigits(), getMinimumFractionDigits());
 756                 appendSuffix(result, suffix, delegate);

 757             } else {
 758                 number = isNegative ? number.negate() : number;
 759                 defaultDecimalFormat.format(number, result, delegate);
 760             }
 761         } else {
 762             number = isNegative ? number.negate() : number;
 763             defaultDecimalFormat.format(number, result, delegate);
 764         }
 765         return result;
 766     }
 767 
 768     /**
 769      * Formats a BigInteger to produce a string representing its compact form.
 770      * @param number    the BigInteger number to format
 771      * @param result    where the text is to be appended
 772      * @param fieldPosition    keeps track on the position of the field within
 773      *                         the returned string. For example, to format
 774      *                         a number {@code 123456789} in the
 775      *                         {@link java.util.Locale#US US locale},
 776      *                         if the given {@code fieldPosition} is


 796         fieldPosition.setEndIndex(0);
 797         return format(number, result, fieldPosition.getFieldDelegate(), false);
 798     }
 799 
 800     private StringBuffer format(BigInteger number, StringBuffer result,
 801             FieldDelegate delegate, boolean formatLong) {
 802 
 803         boolean isNegative = number.signum() == -1;
 804         if (isNegative) {
 805             number = number.negate();
 806         }
 807 
 808         int compactDataIndex = selectCompactPattern(number);
 809         if (compactDataIndex != -1) {
 810             Number divisor = divisors.get(compactDataIndex);
 811             int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue());
 812             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
 813             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
 814             if (!prefix.isEmpty() || !suffix.isEmpty()) {
 815                 appendPrefix(result, prefix, delegate);

 816                 if (number.mod(new BigInteger(divisor.toString()))
 817                         .compareTo(BigInteger.ZERO) == 0) {
 818                     number = number.divide(new BigInteger(divisor.toString()));
 819 
 820                     decimalFormat.setDigitList(number, isNegative, 0);
 821                     decimalFormat.subformatNumber(result, delegate,
 822                             isNegative, true, getMaximumIntegerDigits(),
 823                             getMinimumIntegerDigits(), getMaximumFractionDigits(),
 824                             getMinimumFractionDigits());
 825                 } else {
 826                     // To avoid truncation of fractional part store the value in
 827                     // BigDecimal and follow BigDecimal path instead of
 828                     // BigInteger path
 829                     BigDecimal nDecimal = new BigDecimal(number)
 830                             .divide(new BigDecimal(divisor.toString()), getRoundingMode());
 831                     decimalFormat.setDigitList(nDecimal, isNegative, getMaximumFractionDigits());
 832                     decimalFormat.subformatNumber(result, delegate,
 833                             isNegative, false, getMaximumIntegerDigits(),
 834                             getMinimumIntegerDigits(), getMaximumFractionDigits(),
 835                             getMinimumFractionDigits());
 836                 }
 837                 appendSuffix(result, suffix, delegate);

 838             } else {
 839                 number = isNegative ? number.negate() : number;
 840                 defaultDecimalFormat.format(number, result, delegate, formatLong);
 841             }
 842         } else {
 843             number = isNegative ? number.negate() : number;
 844             defaultDecimalFormat.format(number, result, delegate, formatLong);
 845         }
 846         return result;
 847     }
 848 
 849     /**
 850      * Obtain the designated affix from the appropriate list of affixes,
 851      * based on the given arguments.
 852      */
 853     private String getAffix(boolean isExpanded, boolean isPrefix, boolean isNegative, int compactDataIndex, int iPart) {
 854         return (isExpanded ? (isPrefix ? (isNegative ? negativePrefixes : positivePrefixes) :
 855                                          (isNegative ? negativeSuffixes : positiveSuffixes)) :
 856                              (isPrefix ? (isNegative ? negativePrefixPatterns : positivePrefixPatterns) :
 857                                          (isNegative ? negativeSuffixPatterns : positiveSuffixPatterns)))


 888             FieldDelegate delegate) {
 889         append(result, expandAffix(suffix), delegate,
 890                 getFieldPositions(suffix, NumberFormat.Field.SUFFIX));
 891     }
 892 
 893     /**
 894      * Appends the {@code string} to the {@code result}.
 895      * {@code delegate} is notified of SIGN, PREFIX and/or SUFFIX
 896      * field positions.
 897      * @param result the resulting string, where the text is to be appended
 898      * @param string the text to append
 899      * @param delegate notified of the locations of sub fields
 900      * @param positions a list of {@code FieldPostion} in the given
 901      *                  string
 902      */
 903     private void append(StringBuffer result, String string,
 904             FieldDelegate delegate, List<FieldPosition> positions) {
 905         if (!string.isEmpty()) {
 906             int start = result.length();
 907             result.append(string);
 908             for (int counter = 0; counter < positions.size(); counter++) {
 909                 FieldPosition fp = positions.get(counter);
 910                 Format.Field attribute = fp.getFieldAttribute();
 911                 delegate.formatted(attribute, attribute,
 912                         start + fp.getBeginIndex(),
 913                         start + fp.getEndIndex(), result);
 914             }
 915         }
 916     }
 917 
 918     /**
 919      * Expands an affix {@code pattern} into a string of literals.
 920      * All characters in the pattern are literals unless prefixed by QUOTE.
 921      * The character prefixed by QUOTE is replaced with its respective
 922      * localized literal.
 923      * @param pattern a compact number pattern affix
 924      * @return an expanded affix
 925      */
 926     private String expandAffix(String pattern) {
 927         // Return if no quoted character exists
 928         if (pattern.indexOf(QUOTE) < 0) {
 929             return pattern;


1074         } else if (obj instanceof Long || obj instanceof Integer
1075                 || obj instanceof Short || obj instanceof Byte
1076                 || obj instanceof AtomicInteger || obj instanceof AtomicLong) {
1077             format(((Number) obj).longValue(), sb, delegate);
1078         } else if (obj instanceof BigDecimal) {
1079             format((BigDecimal) obj, sb, delegate);
1080         } else if (obj instanceof BigInteger) {
1081             format((BigInteger) obj, sb, delegate, false);
1082         } else if (obj == null) {
1083             throw new NullPointerException(
1084                     "formatToCharacterIterator must be passed non-null object");
1085         } else {
1086             throw new IllegalArgumentException(
1087                     "Cannot format given Object as a Number");
1088         }
1089         return delegate.getIterator(sb.toString());
1090     }
1091 
1092     /**
1093      * Computes the divisor using minimum integer digits and
1094      * matched pattern index.

1095      * @param minIntDigits string of 0s in compact pattern
1096      * @param patternIndex index of matched compact pattern
1097      * @return divisor value for the number matching the compact
1098      *         pattern at given {@code patternIndex}
1099      */
1100     private Number computeDivisor(String minIntDigits, int patternIndex) {
1101         int count = minIntDigits.length() - 1;
1102         Number matchedValue;
1103         // The divisor value can go above long range, if the compact patterns
1104         // goes above index 18, divisor may need to be stored as BigInteger,
1105         // since long can't store numbers >= 10^19,
1106         if (patternIndex < 19) {
1107             matchedValue = (long) Math.pow(RANGE_MULTIPLIER, patternIndex);
1108         } else {
1109             matchedValue = BigInteger.valueOf(RANGE_MULTIPLIER).pow(patternIndex);
1110         }
1111         Number divisor = matchedValue;
1112         if (count != 0) {
1113             if (matchedValue instanceof BigInteger) {
1114                 BigInteger bigValue = (BigInteger) matchedValue;
1115                 if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count))) < 0) {
1116                     throw new IllegalArgumentException("Invalid Pattern"
1117                             + " [" + compactPatterns[patternIndex]
1118                             + "]: min integer digits specified exceeds the limit"
1119                             + " for the index " + patternIndex);
1120                 }
1121                 divisor = bigValue.divide(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count)));
1122             } else {
1123                 long longValue = (long) matchedValue;
1124                 if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count)) {
1125                     throw new IllegalArgumentException("Invalid Pattern"
1126                             + " [" + compactPatterns[patternIndex]
1127                             + "]: min integer digits specified exceeds the limit"
1128                             + " for the index " + patternIndex);
1129                 }
1130                 divisor = longValue / (long) Math.pow(RANGE_MULTIPLIER, count);







1131             }
1132         }
1133         return divisor;
1134     }
1135 
1136     /**
1137      * Process the series of compact patterns to compute the
1138      * series of prefixes, suffixes and their respective divisor
1139      * value.
1140      *
1141      */
1142     private static final Pattern PLURALS =
1143             Pattern.compile("^\\{(?<plurals>.*)\\}$");
1144     private static final Pattern COUNT_PATTERN =
1145             Pattern.compile("(zero|one|two|few|many|other):((' '|[^ ])+)[ ]*");
1146     private void processCompactPatterns() {
1147         int size = compactPatterns.length;
1148         positivePrefixPatterns = new ArrayList<>(size);
1149         negativePrefixPatterns = new ArrayList<>(size);
1150         positiveSuffixPatterns = new ArrayList<>(size);
1151         negativeSuffixPatterns = new ArrayList<>(size);
1152         divisors = new ArrayList<>(size);
1153 
1154         for (int index = 0; index < size; index++) {
1155             String text = compactPatterns[index];
1156             positivePrefixPatterns.add(new Patterns());
1157             negativePrefixPatterns.add(new Patterns());
1158             positiveSuffixPatterns.add(new Patterns());
1159             negativeSuffixPatterns.add(new Patterns());
1160 
1161             // check if it is the old style
1162             Matcher m = text != null ? PLURALS.matcher(text) : null;
1163             if (m != null && m.matches()) {


1181      */
1182     private Map<String, String> buildPluralRulesMap() {
1183         // length limitation check. 2K for now.
1184         if (pluralRules.length() > 2_048) {
1185             throw new IllegalArgumentException("plural rules is too long (> 2,048)");
1186         }
1187 
1188         try {
1189             return Arrays.stream(pluralRules.split(";"))
1190                 .map(this::validateRule)
1191                 .collect(Collectors.toMap(
1192                         r -> r.replaceFirst(":.*", ""),
1193                         r -> r.replaceFirst("[^:]+:", "")
1194                 ));
1195         } catch (IllegalStateException ise) {
1196             throw new IllegalArgumentException(ise);
1197         }
1198     }
1199 
1200     // Patterns for plurals syntax validation
1201     private final static String EXPR = "([niftvw]{1})\\s*(([/\\%])\\s*(\\d+))*";
1202     private final static String RELATION = "(!{0,1}=)";
1203     private final static String VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)";
1204     private final static String CONDITION = EXPR + "\\s*" +
1205                                              RELATION + "\\s*" +
1206                                              VALUE_RANGE + "\\s*" +
1207                                              "(\\,\\s*" + VALUE_RANGE + ")*";
1208     private final static Pattern PLURALRULES_PATTERN =
1209             Pattern.compile("(zero|one|two|few|many):\\s*" +
1210                             CONDITION +
1211                             "(\\s*(and|or)\\s*" + CONDITION + ")*");
1212 
1213     /**
1214      * Validates a plural rule.
1215      * @param rule rule to validate
1216      * @throws IllegalArgumentException if the {@code rule} has invalid syntax
1217      * @return the input rule (trimmed)
1218      */
1219     private String validateRule(String rule) {
1220         rule = rule.trim();
1221         if (!rule.isEmpty() && !rule.equals("other:")) {
1222             Matcher validator = PLURALRULES_PATTERN.matcher(rule);
1223             if (!validator.matches()) {
1224                 throw new IllegalArgumentException("Invalid plural rules syntax: " + rule);
1225             }
1226         }
1227 


1383                 positiveSuffix = suffix.toString();
1384                 negativePrefix = positivePrefix;
1385                 negativeSuffix = positiveSuffix;
1386             } else {
1387                 negativePrefix = prefix.toString();
1388                 negativeSuffix = suffix.toString();
1389                 gotNegative = true;
1390             }
1391 
1392             // If there is no negative pattern, or if the negative pattern is
1393             // identical to the positive pattern, then prepend the minus sign to
1394             // the positive pattern to form the negative pattern.
1395             if (!gotNegative
1396                     || (negativePrefix.equals(positivePrefix)
1397                     && negativeSuffix.equals(positiveSuffix))) {
1398                 negativeSuffix = positiveSuffix;
1399                 negativePrefix = "'-" + positivePrefix;
1400             }
1401         }
1402 
1403         // If no 0s are specified in a non empty pattern, it is invalid
1404         if (!pattern.isEmpty() && zeros.isEmpty()) {
1405             throw new IllegalArgumentException("Invalid pattern"
1406                     + " [" + pattern + "]: all patterns must include digit"
1407                     + " placement 0s");
1408         }
1409 
1410         // Only if positive affix exists; else put empty strings
1411         if (!positivePrefix.isEmpty() || !positiveSuffix.isEmpty()) {
1412             positivePrefixPatterns.get(index).put(count, positivePrefix);
1413             negativePrefixPatterns.get(index).put(count, negativePrefix);
1414             positiveSuffixPatterns.get(index).put(count, positiveSuffix);
1415             negativeSuffixPatterns.get(index).put(count, negativeSuffix);
1416             if (divisors.size() <= index) {
1417                 divisors.add(computeDivisor(zeros, index));
1418             }
1419         } else {
1420             positivePrefixPatterns.get(index).put(count, "");
1421             negativePrefixPatterns.get(index).put(count, "");
1422             positiveSuffixPatterns.get(index).put(count, "");
1423             negativeSuffixPatterns.get(index).put(count, "");
1424             if (divisors.size() <= index) {
1425                 divisors.add(1L);
1426             }
1427         }
1428     }
1429 


1610             if (matchedPosPrefix.length() > matchedNegPrefix.length()) {
1611                 gotNegative = false;
1612             } else if (matchedPosPrefix.length() < matchedNegPrefix.length()) {
1613                 gotPositive = false;
1614             }
1615         }
1616 
1617         // Update the position and take compact multiplier
1618         // only if it matches the compact prefix, not the default
1619         // prefix; else multiplier should be 1
1620         if (gotPositive) {
1621             position += matchedPosPrefix.length();
1622             cnfMultiplier = matchedPosIndex != -1
1623                     ? divisors.get(matchedPosIndex) : 1L;
1624         } else if (gotNegative) {
1625             position += matchedNegPrefix.length();
1626             cnfMultiplier = matchedNegIndex != -1
1627                     ? divisors.get(matchedNegIndex) : 1L;
1628         }
1629 















1630         digitList.setRoundingMode(getRoundingMode());
1631         boolean[] status = new boolean[STATUS_LENGTH];
1632 
1633         // Call DecimalFormat.subparseNumber() method to parse the
1634         // number part of the input text
1635         position = decimalFormat.subparseNumber(text, position,
1636                 digitList, false, false, status);
1637 
1638         if (position == -1) {
1639             // Unable to parse the number successfully
1640             pos.index = oldStart;
1641             pos.errorIndex = oldStart;
1642             return null;
1643         }
1644 
1645         // If parse integer only is true and the parsing is broken at
1646         // decimal point, then pass/ignore all digits and move pointer
1647         // at the start of suffix, to process the suffix part
1648         if (isParseIntegerOnly()
1649                 && text.charAt(position) == symbols.getDecimalSeparator()) {


1691                         .multiply(new BigDecimal(cnfMultiplier.toString()));
1692             }
1693             if (!status[STATUS_POSITIVE]) {
1694                 bigDecimalResult = bigDecimalResult.negate();
1695             }
1696             return bigDecimalResult;
1697         } else {
1698             Number cnfResult;
1699             if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) {
1700                 long longResult = digitList.getLong();
1701                 cnfResult = generateParseResult(longResult, false,
1702                         longResult < 0, status, cnfMultiplier);
1703             } else {
1704                 cnfResult = generateParseResult(digitList.getDouble(),
1705                         true, false, status, cnfMultiplier);
1706             }
1707             return cnfResult;
1708         }
1709     }
1710 

1711     /**
1712      * Parse the number part in the input text into a number
1713      *
1714      * @param text input text to be parsed
1715      * @param position starting position
1716      * @return the number
1717      */
1718     private static Pattern DIGITS = Pattern.compile("\\p{Nd}+");
1719     private double parseNumberPart(String text, int position) {
1720         if (text.startsWith(symbols.getInfinity(), position)) {
1721             return Double.POSITIVE_INFINITY;
1722         } else if (!text.startsWith(symbols.getNaN(), position)) {
1723             Matcher m = DIGITS.matcher(text);
1724             if (m.find(position)) {
1725                 String digits = m.group();
1726                 int cp = digits.codePointAt(0);
1727                 if (Character.isDigit(cp)) {
1728                     return Double.parseDouble(digits.codePoints()
1729                         .map(Character::getNumericValue)
1730                         .mapToObj(Integer::toString)
1731                         .collect(Collectors.joining()));
1732                 }
1733             }
1734         }
1735         return Double.NaN;
1736     }
1737 
1738     /**


1804             } else {
1805                 return -(double) number;
1806             }
1807         } else {
1808             return number;
1809         }
1810     }
1811 
1812     /**
1813      * Attempts to match the given {@code affix} in the
1814      * specified {@code text}.
1815      */
1816     private boolean matchAffix(String text, int position, String affix,
1817             String defaultAffix, String matchedAffix) {
1818 
1819         // Check with the compact affixes which are non empty and
1820         // do not match with default affix
1821         if (!affix.isEmpty() && !affix.equals(defaultAffix)) {
1822             // Look ahead only for the longer match than the previous match
1823             if (matchedAffix.length() < affix.length()) {
1824                 if (text.regionMatches(position, affix, 0, affix.length())) {
1825                     return true;
1826                 }
1827             }
1828         }
1829         return false;
1830     }
1831 
1832     /**
1833      * Attempts to match given {@code prefix} and {@code suffix} in
1834      * the specified {@code text}.
1835      */
1836     private boolean matchPrefixAndSuffix(String text, int position, String prefix,
1837             String matchedPrefix, String defaultPrefix, String suffix,
1838             String matchedSuffix, String defaultSuffix) {
1839 
1840         // Check the compact pattern suffix only if there is a
1841         // compact prefix match or a default prefix match
1842         // because the compact prefix and suffix should match at the same
1843         // index to obtain the multiplier.
1844         // The prefix match is required because of the possibility of
1845         // same prefix at multiple index, in which case matching the suffix
1846         // is used to obtain the single match


2343     }
2344 
2345     /**
2346      * Creates and returns a copy of this {@code CompactNumberFormat}
2347      * instance.
2348      *
2349      * @return a clone of this instance
2350      */
2351     @Override
2352     public CompactNumberFormat clone() {
2353         CompactNumberFormat other = (CompactNumberFormat) super.clone();
2354         other.compactPatterns = compactPatterns.clone();
2355         other.symbols = (DecimalFormatSymbols) symbols.clone();
2356         return other;
2357     }
2358 
2359     /**
2360      * Abstraction of affix patterns for each "count" tag.
2361      */
2362     private final class Patterns {
2363         private Map<String, String> patternsMap = new HashMap<>();
2364 
2365         void put(String count, String pattern) {
2366             patternsMap.put(count, pattern);
2367         }
2368 
2369         String get(double num) {
2370             return patternsMap.getOrDefault(getPluralCategory(num),
2371                     patternsMap.getOrDefault("other", ""));
2372         }
2373 
2374         Patterns expandAffix() {
2375             Patterns ret = new Patterns();
2376             patternsMap.entrySet().stream()
2377                     .forEach(e -> ret.put(e.getKey(), CompactNumberFormat.this.expandAffix(e.getValue())));
2378             return ret;
2379         }
2380     }
2381 
2382     private final int getIntegerPart(double number, double divisor) {
2383         return BigDecimal.valueOf(number)
2384                 .divide(BigDecimal.valueOf(divisor), roundingMode).intValue();
2385     }
2386 
2387     /**
2388      * Returns LDML's tag from the plurals rules
2389      *
2390      * @param input input number in double type
2391      * @return LDML "count" tag
2392      */
2393     private String getPluralCategory(double input) {
2394         if (rulesMap != null) {
2395             return rulesMap.entrySet().stream()
2396                     .filter(e -> matchPluralRule(e.getValue(), input))
2397                     .map(e -> e.getKey())
2398                     .findFirst()
2399                     .orElse("other");
2400         }
2401 
2402         // defaults to "other"
2403         return "other";
2404     }
2405 
2406     private static boolean matchPluralRule(String condition, double input) {
2407         return Arrays.stream(condition.split("or"))
2408             .anyMatch(and_condition -> {
2409                 return Arrays.stream(and_condition.split("and"))
2410                     .allMatch(r -> relationCheck(r, input));
2411             });
2412     }
2413 
2414     private final static String NAMED_EXPR = "(?<op>[niftvw]{1})\\s*((?<div>[/\\%])\\s*(?<val>\\d+))*";
2415     private final static String NAMED_RELATION = "(?<rel>!{0,1}=)";
2416     private final static String NAMED_VALUE_RANGE = "(?<start>\\d+)\\.\\.(?<end>\\d+)|(?<value>\\d+)";
2417     private final static Pattern EXPR_PATTERN = Pattern.compile(NAMED_EXPR);
2418     private final static Pattern RELATION_PATTERN = Pattern.compile(NAMED_RELATION);
2419     private final static Pattern VALUE_RANGE_PATTERN = Pattern.compile(NAMED_VALUE_RANGE);
2420 
2421     /**
2422      * Checks if the 'input' equals the value, or within the range.
2423      *
2424      * @param valueOrRange A string representing either a single value or a range
2425      * @param input to examine in double
2426      * @return match indicator
2427      */
2428     private static boolean valOrRangeMatches(String valueOrRange, double input) {
2429         Matcher m = VALUE_RANGE_PATTERN.matcher(valueOrRange);
2430 
2431         if (m.find()) {
2432             String value = m.group("value");
2433             if (value != null) {
2434                 return input == Double.parseDouble(value);
2435             } else {


2445      * Checks if the input value satisfies the relation. Each possible value or range is
2446      * separated by a comma ','
2447      *
2448      * @param relation relation string, e.g, "n = 1, 3..5", or "n != 1, 3..5"
2449      * @param input value to examine in double
2450      * @return boolean to indicate whether the relation satisfies or not. If the relation
2451      *  is '=', true if any of the possible value/range satisfies. If the relation is '!=',
2452      *  none of the possible value/range should satisfy to return true.
2453      */
2454     private static boolean relationCheck(String relation, double input) {
2455         Matcher expr = EXPR_PATTERN.matcher(relation);
2456 
2457         if (expr.find()) {
2458             double lop = evalLOperand(expr, input);
2459             Matcher rel = RELATION_PATTERN.matcher(relation);
2460 
2461             if (rel.find(expr.end())) {
2462                 var conditions =
2463                     Arrays.stream(relation.substring(rel.end()).split(","));
2464 
2465                 if (rel.group("rel").equals("!=")) {
2466                     return conditions.noneMatch(c -> valOrRangeMatches(c, lop));
2467                 } else {
2468                     return conditions.anyMatch(c -> valOrRangeMatches(c, lop));
2469                 }
2470             }
2471         }
2472 
2473         return false;
2474     }
2475 
2476     /**
2477      * Evaluates the left operand value.
2478      *
2479      * @param expr Match result
2480      * @param input value to examine in double
2481      * @return resulting double value
2482      */
2483     private static double evalLOperand(Matcher expr, double input) {
2484         double ret = 0;
2485 
2486         if (input == Double.POSITIVE_INFINITY) {
2487             ret =input;
2488         } else {
2489             String op = expr.group("op");
2490             if (op.equals("n") || op.equals("i")) {
2491                 ret = input;
2492             }
2493 
2494             String divop = expr.group("div");
2495             if (divop != null) {
2496                 String divisor = expr.group("val");
2497                 switch (divop) {
2498                     case "%":
2499                         ret %= Double.parseDouble(divisor);
2500                         break;
2501                     case "/":
2502                         ret /= Double.parseDouble(divisor);
2503                         break;
2504                 }
2505             }
2506         }
2507 
2508         return ret;
2509     }
2510 }


  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.


 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}


 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


 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


 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)))


 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;


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()) {


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 


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 


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()) {


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     /**


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


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 {


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 }
< prev index next >