< prev index next >

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

Print this page
rev 60563 : [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}

@@ -587,16 +586,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 (divisor > 0) {
                 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 +654,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 (divisor > 0) {
                 if ((number % divisor == 0)) {
                     number = number / divisor;
                     decimalFormat.setDigitList(number, isNegative, 0);
                     decimalFormat.subformatNumber(result, delegate,
                             isNegative, true, getMaximumIntegerDigits(),

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

@@ -746,16 +749,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 (divisor.doubleValue() > 0) {
                 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 +816,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 (divisor.doubleValue() > 0) {
                 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 +839,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 +910,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);
             }

@@ -1089,47 +1095,55 @@
         return delegate.getIterator(sb.toString());
     }
 
     /**
      * Computes the divisor using minimum integer digits and
-     * matched pattern index.
+     * matched pattern index. If minIntDigits is empty, the divisor
+     * will be negated.
      * @param minIntDigits string of 0s in compact pattern
      * @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);
+            }
+        } else {
+            // no '0's. Indicate it by negating the divisor
+            if (divisor instanceof BigInteger) {
+                divisor = ((BigInteger) divisor).negate();
+            } else {
+                divisor = - divisor.longValue();
             }
         }
         return divisor;
     }
 

@@ -1138,11 +1152,11 @@
      * 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);

@@ -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,17 +1412,10 @@
                 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);

@@ -1625,10 +1632,25 @@
             position += matchedNegPrefix.length();
             cnfMultiplier = matchedNegIndex != -1
                     ? divisors.get(matchedNegIndex) : 1L;
         }
 
+        // If the divisor is negative, no number or suffix exists.
+        // Return the absolute divisor value as the parse result.
+        if (cnfMultiplier instanceof BigInteger) {
+            BigInteger biMultiplier = (BigInteger)cnfMultiplier;
+            if (biMultiplier.signum() == -1) {
+                pos.index = position;
+                return biMultiplier.negate();
+            }
+        } else {
+            if (cnfMultiplier.longValue() < 0) {
+                pos.index = position;
+                return Math.abs(cnfMultiplier.longValue());
+            }
+        }
+
         digitList.setRoundingMode(getRoundingMode());
         boolean[] status = new boolean[STATUS_LENGTH];
 
         // Call DecimalFormat.subparseNumber() method to parse the
         // number part of the input text

@@ -1706,18 +1728,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);

@@ -1819,13 +1841,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;
     }
 

@@ -2358,11 +2378,11 @@
 
     /**
      * Abstraction of affix 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 +2391,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 +2411,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 +2477,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 +2502,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 >