< prev index next >

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

Print this page
rev 60594 : [mq]: 8251499

@@ -40,11 +40,10 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 
 /**
  * <p>
  * {@code CompactNumberFormat} is a concrete subclass of {@code NumberFormat}

@@ -262,10 +261,16 @@
      * beyond long boundary)
      */
     private transient List<Number> divisors;
 
     /**
+     * List of place holders that represent minimum integer digits at each index
+     * for each count.
+     */
+    private transient List<Patterns> placeHolders;
+
+    /**
      * The {@code DecimalFormatSymbols} object used by this format.
      * It contains the symbols used to format numbers. For example,
      * the grouping separator, decimal separator, and so on.
      * This field must not be {@code null}.
      *

@@ -457,11 +462,11 @@
                 this.symbols);
         defaultDecimalFormat.setMaximumFractionDigits(0);
 
         this.pluralRules = pluralRules;
 
-        // Process compact patterns to extract the prefixes, suffixes and
+        // Process compact patterns to extract the prefixes, suffixes, place holders, and
         // divisors
         processCompactPatterns();
     }
 
     /**

@@ -587,16 +592,18 @@
             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
 
             if (!prefix.isEmpty() || !suffix.isEmpty()) {
                 appendPrefix(result, prefix, delegate);
+                if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) {
                 roundedNumber = roundedNumber / divisor;
                 decimalFormat.setDigitList(roundedNumber, isNegative, getMaximumFractionDigits());
                 decimalFormat.subformatNumber(result, delegate, isNegative,
                         false, getMaximumIntegerDigits(), getMinimumIntegerDigits(),
                         getMaximumFractionDigits(), getMinimumFractionDigits());
                 appendSuffix(result, suffix, delegate);
+                }
             } else {
                 defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative);
             }
         } else {
             defaultDecimalFormat.doubleSubformat(number, result, delegate, isNegative);

@@ -653,10 +660,11 @@
             int iPart = getIntegerPart(number, divisor);
             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
             if (!prefix.isEmpty() || !suffix.isEmpty()) {
                 appendPrefix(result, prefix, delegate);
+                if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) {
                 if ((number % divisor == 0)) {
                     number = number / divisor;
                     decimalFormat.setDigitList(number, isNegative, 0);
                     decimalFormat.subformatNumber(result, delegate,
                             isNegative, true, getMaximumIntegerDigits(),

@@ -672,10 +680,11 @@
                             isNegative, false, getMaximumIntegerDigits(),
                             getMinimumIntegerDigits(), getMaximumFractionDigits(),
                             getMinimumFractionDigits());
                 }
                 appendSuffix(result, suffix, delegate);
+                }
             } else {
                 number = isNegative ? -number : number;
                 defaultDecimalFormat.format(number, result, delegate);
             }
         } else {

@@ -746,16 +755,18 @@
             int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue());
             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
             if (!prefix.isEmpty() || !suffix.isEmpty()) {
                 appendPrefix(result, prefix, delegate);
+                if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) {
                 number = number.divide(new BigDecimal(divisor.toString()), getRoundingMode());
                 decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits());
                 decimalFormat.subformatNumber(result, delegate, isNegative,
                         false, getMaximumIntegerDigits(), getMinimumIntegerDigits(),
                         getMaximumFractionDigits(), getMinimumFractionDigits());
                 appendSuffix(result, suffix, delegate);
+                }
             } else {
                 number = isNegative ? number.negate() : number;
                 defaultDecimalFormat.format(number, result, delegate);
             }
         } else {

@@ -811,10 +822,11 @@
             int iPart = getIntegerPart(number.doubleValue(), divisor.doubleValue());
             String prefix = getAffix(false, true, isNegative, compactDataIndex, iPart);
             String suffix = getAffix(false, false, isNegative, compactDataIndex, iPart);
             if (!prefix.isEmpty() || !suffix.isEmpty()) {
                 appendPrefix(result, prefix, delegate);
+                if (!placeHolders.get(compactDataIndex).get(iPart).isEmpty()) {
                 if (number.mod(new BigInteger(divisor.toString()))
                         .compareTo(BigInteger.ZERO) == 0) {
                     number = number.divide(new BigInteger(divisor.toString()));
 
                     decimalFormat.setDigitList(number, isNegative, 0);

@@ -833,10 +845,11 @@
                             isNegative, false, getMaximumIntegerDigits(),
                             getMinimumIntegerDigits(), getMaximumFractionDigits(),
                             getMinimumFractionDigits());
                 }
                 appendSuffix(result, suffix, delegate);
+                }
             } else {
                 number = isNegative ? number.negate() : number;
                 defaultDecimalFormat.format(number, result, delegate, formatLong);
             }
         } else {

@@ -903,12 +916,11 @@
     private void append(StringBuffer result, String string,
             FieldDelegate delegate, List<FieldPosition> positions) {
         if (!string.isEmpty()) {
             int start = result.length();
             result.append(string);
-            for (int counter = 0; counter < positions.size(); counter++) {
-                FieldPosition fp = positions.get(counter);
+            for (FieldPosition fp : positions) {
                 Format.Field attribute = fp.getFieldAttribute();
                 delegate.formatted(attribute, attribute,
                         start + fp.getBeginIndex(),
                         start + fp.getEndIndex(), result);
             }

@@ -1096,40 +1108,40 @@
      * @param patternIndex index of matched compact pattern
      * @return divisor value for the number matching the compact
      *         pattern at given {@code patternIndex}
      */
     private Number computeDivisor(String minIntDigits, int patternIndex) {
-        int count = minIntDigits.length() - 1;
+        int count = minIntDigits.length();
         Number matchedValue;
         // The divisor value can go above long range, if the compact patterns
         // goes above index 18, divisor may need to be stored as BigInteger,
         // since long can't store numbers >= 10^19,
         if (patternIndex < 19) {
             matchedValue = (long) Math.pow(RANGE_MULTIPLIER, patternIndex);
         } else {
             matchedValue = BigInteger.valueOf(RANGE_MULTIPLIER).pow(patternIndex);
         }
         Number divisor = matchedValue;
-        if (count != 0) {
+        if (count > 0) {
             if (matchedValue instanceof BigInteger) {
                 BigInteger bigValue = (BigInteger) matchedValue;
-                if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count))) < 0) {
+                if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1))) < 0) {
                     throw new IllegalArgumentException("Invalid Pattern"
                             + " [" + compactPatterns[patternIndex]
                             + "]: min integer digits specified exceeds the limit"
                             + " for the index " + patternIndex);
                 }
-                divisor = bigValue.divide(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count)));
+                divisor = bigValue.divide(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count - 1)));
             } else {
                 long longValue = (long) matchedValue;
-                if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count)) {
+                if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count - 1)) {
                     throw new IllegalArgumentException("Invalid Pattern"
                             + " [" + compactPatterns[patternIndex]
                             + "]: min integer digits specified exceeds the limit"
                             + " for the index " + patternIndex);
                 }
-                divisor = longValue / (long) Math.pow(RANGE_MULTIPLIER, count);
+                divisor = longValue / (long) Math.pow(RANGE_MULTIPLIER, count - 1);
             }
         }
         return divisor;
     }
 

@@ -1138,27 +1150,29 @@
      * series of prefixes, suffixes and their respective divisor
      * value.
      *
      */
     private static final Pattern PLURALS =
-            Pattern.compile("^\\{(?<plurals>.*)\\}$");
+            Pattern.compile("^\\{(?<plurals>.*)}$");
     private static final Pattern COUNT_PATTERN =
             Pattern.compile("(zero|one|two|few|many|other):((' '|[^ ])+)[ ]*");
     private void processCompactPatterns() {
         int size = compactPatterns.length;
         positivePrefixPatterns = new ArrayList<>(size);
         negativePrefixPatterns = new ArrayList<>(size);
         positiveSuffixPatterns = new ArrayList<>(size);
         negativeSuffixPatterns = new ArrayList<>(size);
         divisors = new ArrayList<>(size);
+        placeHolders = new ArrayList<>(size);
 
         for (int index = 0; index < size; index++) {
             String text = compactPatterns[index];
             positivePrefixPatterns.add(new Patterns());
             negativePrefixPatterns.add(new Patterns());
             positiveSuffixPatterns.add(new Patterns());
             negativeSuffixPatterns.add(new Patterns());
+            placeHolders.add(new Patterns());
 
             // check if it is the old style
             Matcher m = text != null ? PLURALS.matcher(text) : null;
             if (m != null && m.matches()) {
                 final int idx = index;

@@ -1196,17 +1210,17 @@
             throw new IllegalArgumentException(ise);
         }
     }
 
     // Patterns for plurals syntax validation
-    private final static String EXPR = "([niftvw]{1})\\s*(([/\\%])\\s*(\\d+))*";
-    private final static String RELATION = "(!{0,1}=)";
+    private final static String EXPR = "([niftvw])\\s*(([/%])\\s*(\\d+))*";
+    private final static String RELATION = "(!?=)";
     private final static String VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)";
     private final static String CONDITION = EXPR + "\\s*" +
                                              RELATION + "\\s*" +
                                              VALUE_RANGE + "\\s*" +
-                                             "(\\,\\s*" + VALUE_RANGE + ")*";
+                                             "(,\\s*" + VALUE_RANGE + ")*";
     private final static Pattern PLURALRULES_PATTERN =
             Pattern.compile("(zero|one|two|few|many):\\s*" +
                             CONDITION +
                             "(\\s*(and|or)\\s*" + CONDITION + ")*");
 

@@ -1398,31 +1412,26 @@
                 negativeSuffix = positiveSuffix;
                 negativePrefix = "'-" + positivePrefix;
             }
         }
 
-        // If no 0s are specified in a non empty pattern, it is invalid
-        if (!pattern.isEmpty() && zeros.isEmpty()) {
-            throw new IllegalArgumentException("Invalid pattern"
-                    + " [" + pattern + "]: all patterns must include digit"
-                    + " placement 0s");
-        }
-
         // Only if positive affix exists; else put empty strings
         if (!positivePrefix.isEmpty() || !positiveSuffix.isEmpty()) {
             positivePrefixPatterns.get(index).put(count, positivePrefix);
             negativePrefixPatterns.get(index).put(count, negativePrefix);
             positiveSuffixPatterns.get(index).put(count, positiveSuffix);
             negativeSuffixPatterns.get(index).put(count, negativeSuffix);
+            placeHolders.get(index).put(count, zeros);
             if (divisors.size() <= index) {
                 divisors.add(computeDivisor(zeros, index));
             }
         } else {
             positivePrefixPatterns.get(index).put(count, "");
             negativePrefixPatterns.get(index).put(count, "");
             positiveSuffixPatterns.get(index).put(count, "");
             negativeSuffixPatterns.get(index).put(count, "");
+            placeHolders.get(index).put(count, "");
             if (divisors.size() <= index) {
                 divisors.add(1L);
             }
         }
     }

@@ -1615,18 +1624,30 @@
         }
 
         // Update the position and take compact multiplier
         // only if it matches the compact prefix, not the default
         // prefix; else multiplier should be 1
+        // If there's no number part, no need to go further, just
+        // return the multiplier.
         if (gotPositive) {
             position += matchedPosPrefix.length();
-            cnfMultiplier = matchedPosIndex != -1
-                    ? divisors.get(matchedPosIndex) : 1L;
+            if (matchedPosIndex != -1) {
+                cnfMultiplier = divisors.get(matchedPosIndex);
+                if (placeHolders.get(matchedPosIndex).get(num).isEmpty()) {
+                    pos.index = position;
+                    return cnfMultiplier;
+                }
+            }
         } else if (gotNegative) {
             position += matchedNegPrefix.length();
-            cnfMultiplier = matchedNegIndex != -1
-                    ? divisors.get(matchedNegIndex) : 1L;
+            if (matchedNegIndex != -1) {
+                cnfMultiplier = divisors.get(matchedNegIndex);
+                if (placeHolders.get(matchedNegIndex).get(num).isEmpty()) {
+                    pos.index = position;
+                    return cnfMultiplier;
+                }
+            }
         }
 
         digitList.setRoundingMode(getRoundingMode());
         boolean[] status = new boolean[STATUS_LENGTH];
 

@@ -1706,18 +1727,18 @@
             }
             return cnfResult;
         }
     }
 
+    private static final Pattern DIGITS = Pattern.compile("\\p{Nd}+");
     /**
      * Parse the number part in the input text into a number
      *
      * @param text input text to be parsed
      * @param position starting position
      * @return the number
      */
-    private static Pattern DIGITS = Pattern.compile("\\p{Nd}+");
     private double parseNumberPart(String text, int position) {
         if (text.startsWith(symbols.getInfinity(), position)) {
             return Double.POSITIVE_INFINITY;
         } else if (!text.startsWith(symbols.getNaN(), position)) {
             Matcher m = DIGITS.matcher(text);

@@ -1728,10 +1749,13 @@
                     return Double.parseDouble(digits.codePoints()
                         .map(Character::getNumericValue)
                         .mapToObj(Integer::toString)
                         .collect(Collectors.joining()));
                 }
+            } else {
+                // no numbers. return 1.0 for possible no-placeholder pattern
+                return 1.0;
             }
         }
         return Double.NaN;
     }
 

@@ -1819,13 +1843,11 @@
         // Check with the compact affixes which are non empty and
         // do not match with default affix
         if (!affix.isEmpty() && !affix.equals(defaultAffix)) {
             // Look ahead only for the longer match than the previous match
             if (matchedAffix.length() < affix.length()) {
-                if (text.regionMatches(position, affix, 0, affix.length())) {
-                    return true;
-                }
+                return text.regionMatches(position, affix, 0, affix.length());
             }
         }
         return false;
     }
 

@@ -2355,14 +2377,14 @@
         other.symbols = (DecimalFormatSymbols) symbols.clone();
         return other;
     }
 
     /**
-     * Abstraction of affix patterns for each "count" tag.
+     * Abstraction of affix or number (represented by zeros) patterns for each "count" tag.
      */
     private final class Patterns {
-        private Map<String, String> patternsMap = new HashMap<>();
+        private final Map<String, String> patternsMap = new HashMap<>();
 
         void put(String count, String pattern) {
             patternsMap.put(count, pattern);
         }
 

@@ -2371,17 +2393,16 @@
                     patternsMap.getOrDefault("other", ""));
         }
 
         Patterns expandAffix() {
             Patterns ret = new Patterns();
-            patternsMap.entrySet().stream()
-                    .forEach(e -> ret.put(e.getKey(), CompactNumberFormat.this.expandAffix(e.getValue())));
+            patternsMap.forEach((key, value) -> ret.put(key, CompactNumberFormat.this.expandAffix(value)));
             return ret;
         }
     }
 
-    private final int getIntegerPart(double number, double divisor) {
+    private int getIntegerPart(double number, double divisor) {
         return BigDecimal.valueOf(number)
                 .divide(BigDecimal.valueOf(divisor), roundingMode).intValue();
     }
 
     /**

@@ -2392,29 +2413,27 @@
      */
     private String getPluralCategory(double input) {
         if (rulesMap != null) {
             return rulesMap.entrySet().stream()
                     .filter(e -> matchPluralRule(e.getValue(), input))
-                    .map(e -> e.getKey())
+                    .map(Map.Entry::getKey)
                     .findFirst()
                     .orElse("other");
         }
 
         // defaults to "other"
         return "other";
     }
 
     private static boolean matchPluralRule(String condition, double input) {
         return Arrays.stream(condition.split("or"))
-            .anyMatch(and_condition -> {
-                return Arrays.stream(and_condition.split("and"))
-                    .allMatch(r -> relationCheck(r, input));
-            });
+            .anyMatch(and_condition -> Arrays.stream(and_condition.split("and"))
+                .allMatch(r -> relationCheck(r, input)));
     }
 
-    private final static String NAMED_EXPR = "(?<op>[niftvw]{1})\\s*((?<div>[/\\%])\\s*(?<val>\\d+))*";
-    private final static String NAMED_RELATION = "(?<rel>!{0,1}=)";
+    private final static String NAMED_EXPR = "(?<op>[niftvw])\\s*((?<div>[/%])\\s*(?<val>\\d+))*";
+    private final static String NAMED_RELATION = "(?<rel>!?=)";
     private final static String NAMED_VALUE_RANGE = "(?<start>\\d+)\\.\\.(?<end>\\d+)|(?<value>\\d+)";
     private final static Pattern EXPR_PATTERN = Pattern.compile(NAMED_EXPR);
     private final static Pattern RELATION_PATTERN = Pattern.compile(NAMED_RELATION);
     private final static Pattern VALUE_RANGE_PATTERN = Pattern.compile(NAMED_VALUE_RANGE);
 

@@ -2460,11 +2479,11 @@
 
             if (rel.find(expr.end())) {
                 var conditions =
                     Arrays.stream(relation.substring(rel.end()).split(","));
 
-                if (rel.group("rel").equals("!=")) {
+                if (Objects.equals(rel.group("rel"), "!=")) {
                     return conditions.noneMatch(c -> valOrRangeMatches(c, lop));
                 } else {
                     return conditions.anyMatch(c -> valOrRangeMatches(c, lop));
                 }
             }

@@ -2485,24 +2504,20 @@
 
         if (input == Double.POSITIVE_INFINITY) {
             ret =input;
         } else {
             String op = expr.group("op");
-            if (op.equals("n") || op.equals("i")) {
+            if (Objects.equals(op, "n") || Objects.equals(op, "i")) {
                 ret = input;
             }
 
             String divop = expr.group("div");
             if (divop != null) {
                 String divisor = expr.group("val");
                 switch (divop) {
-                    case "%":
-                        ret %= Double.parseDouble(divisor);
-                        break;
-                    case "/":
-                        ret /= Double.parseDouble(divisor);
-                        break;
+                    case "%" -> ret %= Double.parseDouble(divisor);
+                    case "/" -> ret /= Double.parseDouble(divisor);
                 }
             }
         }
 
         return ret;
< prev index next >