< prev index next >

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

Print this page
rev 60594 : [mq]: 8251499

*** 40,50 **** 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} --- 40,49 ----
*** 262,271 **** --- 261,276 ---- * 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,467 **** this.symbols); defaultDecimalFormat.setMaximumFractionDigits(0); this.pluralRules = pluralRules; ! // Process compact patterns to extract the prefixes, suffixes and // divisors processCompactPatterns(); } /** --- 462,472 ---- this.symbols); defaultDecimalFormat.setMaximumFractionDigits(0); this.pluralRules = pluralRules; ! // Process compact patterns to extract the prefixes, suffixes, place holders, and // divisors processCompactPatterns(); } /**
*** 587,602 **** --- 592,609 ---- 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,662 **** --- 660,670 ---- 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,681 **** --- 680,690 ---- isNegative, false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), getMaximumFractionDigits(), getMinimumFractionDigits()); } appendSuffix(result, suffix, delegate); + } } else { number = isNegative ? -number : number; defaultDecimalFormat.format(number, result, delegate); } } else {
*** 746,761 **** --- 755,772 ---- 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,820 **** --- 822,832 ---- 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,842 **** --- 845,855 ---- isNegative, false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), getMaximumFractionDigits(), getMinimumFractionDigits()); } appendSuffix(result, suffix, delegate); + } } else { number = isNegative ? number.negate() : number; defaultDecimalFormat.format(number, result, delegate, formatLong); } } else {
*** 903,914 **** 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); Format.Field attribute = fp.getFieldAttribute(); delegate.formatted(attribute, attribute, start + fp.getBeginIndex(), start + fp.getEndIndex(), result); } --- 916,926 ---- private void append(StringBuffer result, String string, FieldDelegate delegate, List<FieldPosition> positions) { if (!string.isEmpty()) { int start = result.length(); result.append(string); ! for (FieldPosition fp : positions) { Format.Field attribute = fp.getFieldAttribute(); delegate.formatted(attribute, attribute, start + fp.getBeginIndex(), start + fp.getEndIndex(), result); }
*** 1096,1135 **** * @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; 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 (matchedValue instanceof BigInteger) { BigInteger bigValue = (BigInteger) matchedValue; ! if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(RANGE_MULTIPLIER, count))) < 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))); } else { long longValue = (long) matchedValue; ! if (longValue < (long) Math.pow(RANGE_MULTIPLIER, count)) { 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); } } return divisor; } --- 1108,1147 ---- * @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(); 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 (matchedValue instanceof BigInteger) { BigInteger bigValue = (BigInteger) matchedValue; ! 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 - 1))); } else { long longValue = (long) matchedValue; ! 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 - 1); } } return divisor; }
*** 1138,1164 **** * series of prefixes, suffixes and their respective divisor * value. * */ private static final Pattern 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); 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()); // check if it is the old style Matcher m = text != null ? PLURALS.matcher(text) : null; if (m != null && m.matches()) { final int idx = index; --- 1150,1178 ---- * series of prefixes, suffixes and their respective divisor * value. * */ private static final Pattern 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,1212 **** 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 VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)"; private final static String CONDITION = EXPR + "\\s*" + RELATION + "\\s*" + VALUE_RANGE + "\\s*" + ! "(\\,\\s*" + VALUE_RANGE + ")*"; private final static Pattern PLURALRULES_PATTERN = Pattern.compile("(zero|one|two|few|many):\\s*" + CONDITION + "(\\s*(and|or)\\s*" + CONDITION + ")*"); --- 1210,1226 ---- throw new IllegalArgumentException(ise); } } // Patterns for plurals syntax validation ! 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 + ")*"; private final static Pattern PLURALRULES_PATTERN = Pattern.compile("(zero|one|two|few|many):\\s*" + CONDITION + "(\\s*(and|or)\\s*" + CONDITION + ")*");
*** 1398,1428 **** 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); 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, ""); if (divisors.size() <= index) { divisors.add(1L); } } } --- 1412,1437 ---- negativeSuffix = positiveSuffix; negativePrefix = "'-" + positivePrefix; } } // 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,1632 **** } // Update the position and take compact multiplier // only if it matches the compact prefix, not the default // prefix; else multiplier should be 1 ! if (gotPositive) { ! position += matchedPosPrefix.length(); ! cnfMultiplier = matchedPosIndex != -1 ! ? divisors.get(matchedPosIndex) : 1L; ! } else if (gotNegative) { ! position += matchedNegPrefix.length(); ! cnfMultiplier = matchedNegIndex != -1 ! ? divisors.get(matchedNegIndex) : 1L; } digitList.setRoundingMode(getRoundingMode()); boolean[] status = new boolean[STATUS_LENGTH]; --- 1624,1645 ---- } // 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 || gotNegative) { ! position += gotPositive ? matchedPosPrefix.length(): matchedNegPrefix.length(); ! int matchedIndex = gotPositive ? matchedPosIndex : matchedNegIndex; ! if (matchedIndex != -1) { ! cnfMultiplier = divisors.get(matchedIndex); ! if (placeHolders.get(matchedIndex).get(num).isEmpty()) { ! pos.index = position; ! return cnfMultiplier; ! } ! } } digitList.setRoundingMode(getRoundingMode()); boolean[] status = new boolean[STATUS_LENGTH];
*** 1706,1723 **** } return cnfResult; } } /** * 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); --- 1719,1736 ---- } 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 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,1737 **** --- 1741,1753 ---- 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,1831 **** // 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 false; } --- 1835,1845 ---- // 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()) { ! return text.regionMatches(position, affix, 0, affix.length()); } } return false; }
*** 2355,2368 **** other.symbols = (DecimalFormatSymbols) symbols.clone(); return other; } /** ! * Abstraction of affix patterns for each "count" tag. */ private final class Patterns { ! private Map<String, String> patternsMap = new HashMap<>(); void put(String count, String pattern) { patternsMap.put(count, pattern); } --- 2369,2382 ---- other.symbols = (DecimalFormatSymbols) symbols.clone(); return other; } /** ! * Abstraction of affix or number (represented by zeros) patterns for each "count" tag. */ private final class Patterns { ! private final Map<String, String> patternsMap = new HashMap<>(); void put(String count, String pattern) { patternsMap.put(count, pattern); }
*** 2371,2387 **** patternsMap.getOrDefault("other", "")); } Patterns expandAffix() { Patterns ret = new Patterns(); ! patternsMap.entrySet().stream() ! .forEach(e -> ret.put(e.getKey(), CompactNumberFormat.this.expandAffix(e.getValue()))); return ret; } } ! private final int getIntegerPart(double number, double divisor) { return BigDecimal.valueOf(number) .divide(BigDecimal.valueOf(divisor), roundingMode).intValue(); } /** --- 2385,2400 ---- patternsMap.getOrDefault("other", "")); } Patterns expandAffix() { Patterns ret = new Patterns(); ! patternsMap.forEach((key, value) -> ret.put(key, CompactNumberFormat.this.expandAffix(value))); return ret; } } ! private int getIntegerPart(double number, double divisor) { return BigDecimal.valueOf(number) .divide(BigDecimal.valueOf(divisor), roundingMode).intValue(); } /**
*** 2392,2420 **** */ private String getPluralCategory(double input) { if (rulesMap != null) { return rulesMap.entrySet().stream() .filter(e -> matchPluralRule(e.getValue(), input)) ! .map(e -> e.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)); ! }); } ! 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_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); --- 2405,2431 ---- */ private String getPluralCategory(double input) { if (rulesMap != null) { return rulesMap.entrySet().stream() .filter(e -> matchPluralRule(e.getValue(), input)) ! .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 -> Arrays.stream(and_condition.split("and")) ! .allMatch(r -> relationCheck(r, input))); } ! 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,2470 **** if (rel.find(expr.end())) { var conditions = Arrays.stream(relation.substring(rel.end()).split(",")); ! if (rel.group("rel").equals("!=")) { return conditions.noneMatch(c -> valOrRangeMatches(c, lop)); } else { return conditions.anyMatch(c -> valOrRangeMatches(c, lop)); } } --- 2471,2481 ---- if (rel.find(expr.end())) { var conditions = Arrays.stream(relation.substring(rel.end()).split(",")); ! if (Objects.equals(rel.group("rel"), "!=")) { return conditions.noneMatch(c -> valOrRangeMatches(c, lop)); } else { return conditions.anyMatch(c -> valOrRangeMatches(c, lop)); } }
*** 2485,2508 **** if (input == Double.POSITIVE_INFINITY) { ret =input; } else { String op = expr.group("op"); ! if (op.equals("n") || op.equals("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; } } } return ret; --- 2496,2515 ---- if (input == Double.POSITIVE_INFINITY) { ret =input; } else { String op = expr.group("op"); ! 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); ! case "/" -> ret /= Double.parseDouble(divisor); } } } return ret;
< prev index next >