--- old/make/jdk/src/classes/build/tools/cldrconverter/AbstractLDMLHandler.java 2018-11-28 14:09:02.266276887 +0530 +++ new/make/jdk/src/classes/build/tools/cldrconverter/AbstractLDMLHandler.java 2018-11-28 14:09:01.958276887 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -151,6 +151,19 @@ } } + void pushStringListEntry(String qName, Attributes attributes, String key) { + if (!pushIfIgnored(qName, attributes)) { + currentContainer = new StringListEntry(qName, currentContainer, key); + } + } + + void pushStringListElement(String qName, Attributes attributes, int index) { + if (!pushIfIgnored(qName, attributes)) { + currentContainer = new StringListElement(qName, currentContainer, index); + } + } + + private boolean pushIfIgnored(String qName, Attributes attributes) { if (isIgnored(attributes) || currentContainer instanceof IgnoredContainer) { pushIgnoredContainer(qName); --- old/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java 2018-11-28 14:09:03.042276887 +0530 +++ new/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java 2018-11-28 14:09:02.762276887 +0530 @@ -53,6 +53,10 @@ "NumberPatterns/percent" }; + private final static String[] COMPACT_NUMBER_PATTERN_KEYS = { + "short.CompactNumberPatterns", + "long.CompactNumberPatterns"}; + private final static String[] NUMBER_ELEMENT_KEYS = { "NumberElements/decimal", "NumberElements/group", @@ -228,6 +232,16 @@ } } + for (String k : COMPACT_NUMBER_PATTERN_KEYS) { + List patterns = (List) myMap.remove(k); + if (patterns != null) { + // Replace any null entry with empty strings. + String[] arrPatterns = patterns.stream() + .map(s -> s == null ? "" : s).toArray(String[]::new); + myMap.put(k, arrPatterns); + } + } + // if myMap has any of NUMBER_ELEMENT_KEYS, create a complete NumberElements. String defaultScript = (String) myMap.get("DefaultNumberingSystem"); @SuppressWarnings("unchecked") --- old/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java 2018-11-28 14:09:03.782276887 +0530 +++ new/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java 2018-11-28 14:09:03.474276887 +0530 @@ -888,6 +888,8 @@ copyIfPresent(map, "NumberElements", formatData); } copyIfPresent(map, "NumberPatterns", formatData); + copyIfPresent(map, "short.CompactNumberPatterns", formatData); + copyIfPresent(map, "long.CompactNumberPatterns", formatData); // put extra number elements for available scripts into formatData, if it is "root" if (id.equals("root")) { --- old/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java 2018-11-28 14:09:04.494276887 +0530 +++ new/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java 2018-11-28 14:09:04.214276887 +0530 @@ -52,6 +52,8 @@ private final String id; private String currentContext = ""; // "format"/"stand-alone" private String currentWidth = ""; // "wide"/"narrow"/"abbreviated" + private String currentStyle = ""; // short, long for decimalFormat + private String compactCount = ""; // one or other for decimalFormat LDMLParseHandler(String id) { this.id = id; @@ -503,13 +505,85 @@ // Number format information // case "decimalFormatLength": - if (attributes.getValue("type") == null) { - // skipping type="short" data - // for FormatData - // copy string for later assembly into NumberPatterns + String type = attributes.getValue("type"); + if (null == type) { + // format data for decimal number format pushStringEntry(qName, attributes, "NumberPatterns/decimal"); + currentStyle = type; } else { - pushIgnoredContainer(qName); + switch (type) { + case "short": + case "long": + // considering "short" and long for + // compact number formatting patterns + pushKeyContainer(qName, attributes, type); + currentStyle = type; + break; + default: + pushIgnoredContainer(qName); + break; + } + } + break; + case "decimalFormat": + if(currentStyle == null) { + pushContainer(qName, attributes); + } else { + switch (currentStyle) { + case "short": + pushStringListEntry(qName, attributes, + currentStyle+".CompactNumberPatterns"); + break; + case "long": + pushStringListEntry(qName, attributes, + currentStyle+".CompactNumberPatterns"); + break; + default: + pushIgnoredContainer(qName); + break; + } + } + break; + case "pattern": + String containerName = currentContainer.getqName(); + if (containerName.equals("decimalFormat")) { + if (currentStyle == null) { + pushContainer(qName, attributes); + } else { + // The compact number patterns parsing assumes that the order + // of patterns are always in the increasing order of their + // type attribute i.e. type = 1000... + // Between the inflectional forms for a type (e.g. + // count = "one" and count = "other" for type = 1000), it is + // assumed that the count = "one" always appears before + // count = "other" + switch (currentStyle) { + case "short": + case "long": + String count = attributes.getValue("count"); + // first pattern of count = "one" or count = "other" + if ((count.equals("one") || count.equals("other")) + && compactCount.equals("")) { + compactCount = count; + pushStringListElement(qName, attributes, + (int) Math.log10(Double.parseDouble(attributes.getValue("type")))); + } else if ((count.equals("one") || count.equals("other")) + && compactCount.equals(count)) { + // extract patterns with similar "count" + // attribute value + pushStringListElement(qName, attributes, + (int) Math.log10(Double.parseDouble(attributes.getValue("type")))); + } else { + pushIgnoredContainer(qName); + } + break; + default: + pushIgnoredContainer(qName); + break; + } + } + } else { + pushContainer(qName, attributes); } break; case "currencyFormatLength": @@ -676,10 +750,9 @@ // "alias" for root case "alias": { - if (id.equals("root") && - !isIgnored(attributes) && - currentCalendarType != null && - !currentCalendarType.lname().startsWith("islamic-")) { // ignore Islamic variants + if (id.equals("root") && !isIgnored(attributes) + && ((currentContainer.getqName().equals("decimalFormatLength")) + || (currentCalendarType != null && !currentCalendarType.lname().startsWith("islamic-")))) { // ignore islamic variants pushAliasEntry(qName, attributes, attributes.getValue("path")); } else { pushIgnoredContainer(qName); @@ -831,6 +904,9 @@ case "dayPeriods": case "eras": break; + case "decimalFormatLength": // used for compact number formatting patterns + keyName = type + ".CompactNumberPatterns"; + break; default: keyName = ""; break; @@ -868,6 +944,14 @@ if (start != -1) { width = path.substring(start+typeKey.length(), path.indexOf("']", start)); } + + // used for compact number formatting patterns aliases + typeKey = "decimalFormatLength[@type='"; + start = path.indexOf(typeKey); + if (start != -1) { + String style = path.substring(start + typeKey.length(), path.indexOf("']", start)); + return toJDKKey(qName, "", style); + } return calType + "." + toJDKKey(qName, context, width); } @@ -926,7 +1010,11 @@ currentContext = ""; putIfEntry(); break; - + case "decimalFormatLength": + currentStyle = ""; + compactCount = ""; + putIfEntry(); + break; default: putIfEntry(); } @@ -937,22 +1025,28 @@ if (currentContainer instanceof AliasEntry) { Entry entry = (Entry) currentContainer; String containerqName = entry.getParent().getqName(); - Set keyNames = populateAliasKeys(containerqName, currentContext, currentWidth); - if (!keyNames.isEmpty()) { - for (String keyName : keyNames) { - String[] tmp = keyName.split(",", 3); - String calType = currentCalendarType.lname(); - String src = calType+"."+tmp[0]; - String target = getTarget( - entry.getKey(), - calType, - tmp[1].length()>0 ? tmp[1] : currentContext, - tmp[2].length()>0 ? tmp[2] : currentWidth); - if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) { - target = target.substring(0, target.indexOf('.'))+"."+tmp[0]; + if (containerqName.equals("decimalFormatLength")) { + String srcKey = toJDKKey(containerqName, "", currentStyle); + String targetKey = getTarget(entry.getKey(), "", "", ""); + CLDRConverter.aliases.put(srcKey, targetKey); + } else { + Set keyNames = populateAliasKeys(containerqName, currentContext, currentWidth); + if (!keyNames.isEmpty()) { + for (String keyName : keyNames) { + String[] tmp = keyName.split(",", 3); + String calType = currentCalendarType.lname(); + String src = calType+"."+tmp[0]; + String target = getTarget( + entry.getKey(), + calType, + tmp[1].length()>0 ? tmp[1] : currentContext, + tmp[2].length()>0 ? tmp[2] : currentWidth); + if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) { + target = target.substring(0, target.indexOf('.'))+"."+tmp[0]; + } + CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""), + target.replaceFirst("^gregorian.", "")); } - CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""), - target.replaceFirst("^gregorian.", "")); } } } else if (currentContainer instanceof Entry) { --- old/src/java.base/share/classes/java/text/DecimalFormat.java 2018-11-28 14:09:05.290276887 +0530 +++ new/src/java.base/share/classes/java/text/DecimalFormat.java 2018-11-28 14:09:04.986276887 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,9 +48,6 @@ import java.util.ArrayList; import java.util.Currency; import java.util.Locale; -import java.util.ResourceBundle; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import sun.util.locale.provider.LocaleProviderAdapter; @@ -157,7 +154,7 @@ * used. So "#,##,###,####" == "######,####" == * "##,####,####". * - *

Special Pattern Characters

+ *

Special Pattern Characters

* *

Many characters in a pattern are taken literally; they are matched during * parsing and output unchanged during formatting. Special characters, on the @@ -572,14 +569,11 @@ * mode being set to RoundingMode.UNNECESSARY * @return The formatted number string */ - private StringBuffer format(double number, StringBuffer result, + StringBuffer format(double number, StringBuffer result, FieldDelegate delegate) { - if (Double.isNaN(number) || - (Double.isInfinite(number) && multiplier == 0)) { - int iFieldStart = result.length(); - result.append(symbols.getNaN()); - delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, - iFieldStart, result.length(), result); + + boolean nanOrInfinity = handleNaN(number, result, delegate); + if (nanOrInfinity) { return result; } @@ -599,6 +593,56 @@ number *= multiplier; } + nanOrInfinity = handleInfinity(number, result, delegate, isNegative); + if (nanOrInfinity) { + return result; + } + + if (isNegative) { + number = -number; + } + + // at this point we are guaranteed a nonnegative finite number. + assert (number >= 0 && !Double.isInfinite(number)); + return doubleSubformat(number, result, delegate, isNegative); + } + + /** + * Checks if the given {@code number} is {@code Double.NaN}. if yes; + * appends the NaN symbol to the result string. The NaN string is + * determined by the DecimalFormatSymbols object. + * @param number the double number to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return true, if number is a NaN; false otherwise + */ + boolean handleNaN(double number, StringBuffer result, + FieldDelegate delegate) { + if (Double.isNaN(number) + || (Double.isInfinite(number) && multiplier == 0)) { + int iFieldStart = result.length(); + result.append(symbols.getNaN()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + return true; + } + return false; + } + + /** + * Checks if the given {@code number} is {@code Double.NEGATIVE_INFINITY} + * or {@code Double.POSITIVE_INFINITY}. if yes; + * appends the infinity string to the result string. The infinity string is + * determined by the DecimalFormatSymbols object. + * @param number the double number to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @param isNegative whether the given {@code number} is negative + * @return true, if number is a {@code Double.NEGATIVE_INFINITY} or + * {@code Double.POSITIVE_INFINITY}; false otherwise + */ + boolean handleInfinity(double number, StringBuffer result, + FieldDelegate delegate, boolean isNegative) { if (Double.isInfinite(number)) { if (isNegative) { append(result, negativePrefix, delegate, @@ -621,27 +665,24 @@ getPositiveSuffixFieldPositions(), Field.SIGN); } - return result; - } - - if (isNegative) { - number = -number; + return true; } + return false; + } - // at this point we are guaranteed a nonnegative finite number. - assert(number >= 0 && !Double.isInfinite(number)); - - synchronized(digitList) { + StringBuffer doubleSubformat(double number, StringBuffer result, + FieldDelegate delegate, boolean isNegative) { + synchronized (digitList) { int maxIntDigits = super.getMaximumIntegerDigits(); int minIntDigits = super.getMinimumIntegerDigits(); int maxFraDigits = super.getMaximumFractionDigits(); int minFraDigits = super.getMinimumFractionDigits(); - digitList.set(isNegative, number, useExponentialNotation ? - maxIntDigits + maxFraDigits : maxFraDigits, - !useExponentialNotation); + digitList.set(isNegative, number, useExponentialNotation + ? maxIntDigits + maxFraDigits : maxFraDigits, + !useExponentialNotation); return subformat(result, delegate, isNegative, false, - maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); } } @@ -683,7 +724,7 @@ * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ - private StringBuffer format(long number, StringBuffer result, + StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) { boolean isNegative = (number < 0); if (isNegative) { @@ -774,7 +815,7 @@ * mode being set to RoundingMode.UNNECESSARY * @return The formatted number string */ - private StringBuffer format(BigDecimal number, StringBuffer result, + StringBuffer format(BigDecimal number, StringBuffer result, FieldDelegate delegate) { if (multiplier != 1) { number = number.multiply(getBigDecimalMultiplier()); @@ -835,7 +876,7 @@ * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ - private StringBuffer format(BigInteger number, StringBuffer result, + StringBuffer format(BigInteger number, StringBuffer result, FieldDelegate delegate, boolean formatLong) { if (multiplier != 1) { number = number.multiply(getBigIntegerMultiplier()); @@ -917,7 +958,7 @@ return delegate.getIterator(sb.toString()); } - // ==== Begin fast-path formating logic for double ========================= + // ==== Begin fast-path formatting logic for double ========================= /* Fast-path formatting will be used for format(double ...) methods iff a * number of conditions are met (see checkAndSetFastPathStatus()): @@ -1662,6 +1703,26 @@ } + /** + * Sets the {@code DigitList} used by this {@code DecimalFormat} + * instance. + * @param number the number to format + * @param isNegative true, if the number is negative; false otherwise + * @param maxDigits the max digits + */ + void setDigitList(Number number, boolean isNegative, int maxDigits) { + + if (number instanceof Double) { + digitList.set(isNegative, (Double) number, maxDigits, true); + } else if (number instanceof BigDecimal) { + digitList.set(isNegative, (BigDecimal) number, maxDigits, true); + } else if (number instanceof Long) { + digitList.set(isNegative, (Long) number, maxDigits); + } else if (number instanceof BigInteger) { + digitList.set(isNegative, (BigInteger) number, maxDigits); + } + } + // ======== End fast-path formating logic for double ========================= /** @@ -1669,29 +1730,59 @@ * be filled in with the correct digits. */ private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, - boolean isNegative, boolean isInteger, - int maxIntDigits, int minIntDigits, - int maxFraDigits, int minFraDigits) { - // NOTE: This isn't required anymore because DigitList takes care of this. - // - // // The negative of the exponent represents the number of leading - // // zeros between the decimal and the first non-zero digit, for - // // a value < 0.1 (e.g., for 0.00123, -fExponent == 2). If this - // // is more than the maximum fraction digits, then we have an underflow - // // for the printed representation. We recognize this here and set - // // the DigitList representation to zero in this situation. - // - // if (-digitList.decimalAt >= getMaximumFractionDigits()) - // { - // digitList.count = 0; - // } + boolean isNegative, boolean isInteger, + int maxIntDigits, int minIntDigits, + int maxFraDigits, int minFraDigits) { + + // process prefix + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + // process number + subformatNumber(result, delegate, isNegative, isInteger, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + + // process suffix + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + /** + * Subformats number part using the {@code DigitList} of this + * {@code DecimalFormat} instance. + * @param result where the text is to be appended + * @param delegate notified of the location of sub fields + * @param isNegative true, if the number is negative; false otherwise + * @param isInteger true, if the number is an integer; false otherwise + * @param maxIntDigits maximum integer digits + * @param minIntDigits minimum integer digits + * @param maxFraDigits maximum fraction digits + * @param minFraDigits minimum fraction digits + */ + void subformatNumber(StringBuffer result, FieldDelegate delegate, + boolean isNegative, boolean isInteger, + int maxIntDigits, int minIntDigits, + int maxFraDigits, int minFraDigits) { + + char grouping = symbols.getGroupingSeparator(); char zero = symbols.getZeroDigit(); int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero - char grouping = symbols.getGroupingSeparator(); + char decimal = isCurrencyFormat ? - symbols.getMonetaryDecimalSeparator() : - symbols.getDecimalSeparator(); + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); /* Per bug 4147706, DecimalFormat must respect the sign of numbers which * format as zero. This allows sensible computations and preserves @@ -1703,14 +1794,6 @@ digitList.decimalAt = 0; // Normalize } - if (isNegative) { - append(result, negativePrefix, delegate, - getNegativePrefixFieldPositions(), Field.SIGN); - } else { - append(result, positivePrefix, delegate, - getPositivePrefixFieldPositions(), Field.SIGN); - } - if (useExponentialNotation) { int iFieldStart = result.length(); int iFieldEnd = -1; @@ -1719,7 +1802,6 @@ // Minimum integer digits are handled in exponential format by // adjusting the exponent. For example, 0.01234 with 3 minimum // integer digits is "123.4E-4". - // Maximum integer digits are interpreted as indicating the // repeating range. This is useful for engineering notation, in // which the exponent is restricted to a multiple of 3. For @@ -1782,8 +1864,8 @@ fFieldStart = result.length(); } result.append((i < digitList.count) ? - (char)(digitList.digits[i] + zeroDelta) : - zero); + (char)(digitList.digits[i] + zeroDelta) : + zero); } if (decimalSeparatorAlwaysShown && totalDigits == integerDigits) { @@ -1802,17 +1884,17 @@ iFieldEnd = result.length(); } delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, - iFieldStart, iFieldEnd, result); + iFieldStart, iFieldEnd, result); if (addedDecimalSeparator) { delegate.formatted(Field.DECIMAL_SEPARATOR, - Field.DECIMAL_SEPARATOR, - iFieldEnd, fFieldStart, result); + Field.DECIMAL_SEPARATOR, + iFieldEnd, fFieldStart, result); } if (fFieldStart == -1) { fFieldStart = result.length(); } delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, - fFieldStart, result.length(), result); + fFieldStart, result.length(), result); // The exponent is output using the pattern-specified minimum // exponent digits. There is no maximum limit to the exponent @@ -1823,7 +1905,7 @@ result.append(symbols.getExponentSeparator()); delegate.formatted(Field.EXPONENT_SYMBOL, Field.EXPONENT_SYMBOL, - fieldStart, result.length(), result); + fieldStart, result.length(), result); // For zero values, we force the exponent to zero. We // must do this here, and not earlier, because the value @@ -1838,7 +1920,7 @@ fieldStart = result.length(); result.append(symbols.getMinusSign()); delegate.formatted(Field.EXPONENT_SIGN, Field.EXPONENT_SIGN, - fieldStart, result.length(), result); + fieldStart, result.length(), result); } digitList.set(negativeExponent, exponent); @@ -1849,10 +1931,10 @@ } for (int i=0; i0 && (groupingSize != 0) && - (i % groupingSize == 0)) { + (i % groupingSize == 0)) { int gStart = result.length(); result.append(grouping); delegate.formatted(Field.GROUPING_SEPARATOR, - Field.GROUPING_SEPARATOR, gStart, - result.length(), result); + Field.GROUPING_SEPARATOR, gStart, + result.length(), result); } } // Determine whether or not there are any printable fractional // digits. If we've used up the digits we know there aren't. boolean fractionPresent = (minFraDigits > 0) || - (!isInteger && digitIndex < digitList.count); + (!isInteger && digitIndex < digitList.count); // If there is no fraction present, and we haven't printed any // integer digits, then print a zero. Otherwise we won't print @@ -1911,7 +1993,7 @@ } delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, - iFieldStart, result.length(), result); + iFieldStart, result.length(), result); // Output the decimal separator if we always do so. int sStart = result.length(); @@ -1921,8 +2003,8 @@ if (sStart != result.length()) { delegate.formatted(Field.DECIMAL_SEPARATOR, - Field.DECIMAL_SEPARATOR, - sStart, result.length(), result); + Field.DECIMAL_SEPARATOR, + sStart, result.length(), result); } int fFieldStart = result.length(); @@ -1934,7 +2016,7 @@ // we have an integer, so there is no fractional stuff to // display, or we're out of significant digits. if (i >= minFraDigits && - (isInteger || digitIndex >= digitList.count)) { + (isInteger || digitIndex >= digitList.count)) { break; } @@ -1957,18 +2039,8 @@ // Record field information for caller. delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, - fFieldStart, result.length(), result); - } - - if (isNegative) { - append(result, negativeSuffix, delegate, - getNegativeSuffixFieldPositions(), Field.SIGN); - } else { - append(result, positiveSuffix, delegate, - getPositiveSuffixFieldPositions(), Field.SIGN); + fFieldStart, result.length(), result); } - - return result; } /** @@ -2209,19 +2281,18 @@ * whether the value was infinite and whether it was positive. */ private final boolean subparse(String text, ParsePosition parsePosition, - String positivePrefix, String negativePrefix, - DigitList digits, boolean isExponent, - boolean status[]) { + String positivePrefix, String negativePrefix, + DigitList digits, boolean isExponent, + boolean status[]) { int position = parsePosition.index; int oldStart = parsePosition.index; - int backup; boolean gotPositive, gotNegative; // check for positivePrefix; take longest gotPositive = text.regionMatches(position, positivePrefix, 0, - positivePrefix.length()); + positivePrefix.length()); gotNegative = text.regionMatches(position, negativePrefix, 0, - negativePrefix.length()); + negativePrefix.length()); if (gotPositive && gotNegative) { if (positivePrefix.length() > negativePrefix.length()) { @@ -2240,10 +2311,75 @@ return false; } + position = subparseNumber(text, position, digits, true, isExponent, status); + if (position == -1) { + parsePosition.index = oldStart; + parsePosition.errorIndex = oldStart; + return false; + } + + // check for suffix + if (!isExponent) { + if (gotPositive) { + gotPositive = text.regionMatches(position,positiveSuffix,0, + positiveSuffix.length()); + } + if (gotNegative) { + gotNegative = text.regionMatches(position,negativeSuffix,0, + negativeSuffix.length()); + } + + // if both match, take longest + if (gotPositive && gotNegative) { + if (positiveSuffix.length() > negativeSuffix.length()) { + gotNegative = false; + } else if (positiveSuffix.length() < negativeSuffix.length()) { + gotPositive = false; + } + } + + // fail if neither or both + if (gotPositive == gotNegative) { + parsePosition.errorIndex = position; + return false; + } + + parsePosition.index = position + + (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success! + } else { + parsePosition.index = position; + } + + status[STATUS_POSITIVE] = gotPositive; + if (parsePosition.index == oldStart) { + parsePosition.errorIndex = position; + return false; + } + return true; + } + + /** + * Parses a number from the given {@code text}. The text is parsed + * beginning at position, until an unparseable character is seen. + * + * @param text the string to parse + * @param position the position at which parsing begins + * @param digits the DigitList to set to the parsed value + * @param checkExponent whether to check for exponential number + * @param isExponent if the exponential part is encountered + * @param status upon return contains boolean status flags indicating + * whether the value is infinite and whether it is + * positive + * @return returns the position of the first unparseable character or + * -1 in case of no valid number parsed + */ + int subparseNumber(String text, int position, + DigitList digits, boolean checkExponent, + boolean isExponent, boolean status[]) { // process digits or Inf, find decimal position status[STATUS_INFINITE] = false; if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0, - symbols.getInfinity().length())) { + symbols.getInfinity().length())) { position += symbols.getInfinity().length(); status[STATUS_INFINITE] = true; } else { @@ -2257,8 +2393,8 @@ digits.decimalAt = digits.count = 0; char zero = symbols.getZeroDigit(); char decimal = isCurrencyFormat ? - symbols.getMonetaryDecimalSeparator() : - symbols.getDecimalSeparator(); + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); char grouping = symbols.getGroupingSeparator(); String exponentString = symbols.getExponentSeparator(); boolean sawDecimal = false; @@ -2270,7 +2406,7 @@ // pin when the maximum allowable digits is reached. int digitCount = 0; - backup = -1; + int backup = -1; for (; position < text.length(); ++position) { char ch = text.charAt(position); @@ -2334,15 +2470,15 @@ // require that they be followed by a digit. Otherwise // we backup and reprocess them. backup = position; - } else if (!isExponent && text.regionMatches(position, exponentString, 0, exponentString.length()) - && !sawExponent) { + } else if (checkExponent && !isExponent && text.regionMatches(position, exponentString, 0, exponentString.length()) + && !sawExponent) { // Process the exponent by recursively calling this method. - ParsePosition pos = new ParsePosition(position + exponentString.length()); + ParsePosition pos = new ParsePosition(position + exponentString.length()); boolean[] stat = new boolean[STATUS_LENGTH]; DigitList exponentDigits = new DigitList(); if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) && - exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { + exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { position = pos.index; // Advance past the exponent exponent = (int)exponentDigits.getLong(); if (!stat[STATUS_POSITIVE]) { @@ -2373,50 +2509,11 @@ // parse "$" with pattern "$#0.00". (return index 0 and error // index 1). if (!sawDigit && digitCount == 0) { - parsePosition.index = oldStart; - parsePosition.errorIndex = oldStart; - return false; + return -1; } } + return position; - // check for suffix - if (!isExponent) { - if (gotPositive) { - gotPositive = text.regionMatches(position,positiveSuffix,0, - positiveSuffix.length()); - } - if (gotNegative) { - gotNegative = text.regionMatches(position,negativeSuffix,0, - negativeSuffix.length()); - } - - // if both match, take longest - if (gotPositive && gotNegative) { - if (positiveSuffix.length() > negativeSuffix.length()) { - gotNegative = false; - } else if (positiveSuffix.length() < negativeSuffix.length()) { - gotPositive = false; - } - } - - // fail if neither or both - if (gotPositive == gotNegative) { - parsePosition.errorIndex = position; - return false; - } - - parsePosition.index = position + - (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success! - } else { - parsePosition.index = position; - } - - status[STATUS_POSITIVE] = gotPositive; - if (parsePosition.index == oldStart) { - parsePosition.errorIndex = position; - return false; - } - return true; } /** --- old/src/java.base/share/classes/java/text/NumberFormat.java 2018-11-28 14:09:06.122276887 +0530 +++ new/src/java.base/share/classes/java/text/NumberFormat.java 2018-11-28 14:09:05.842276887 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,13 +47,11 @@ import java.text.spi.NumberFormatProvider; import java.util.Currency; import java.util.HashMap; -import java.util.Hashtable; import java.util.Locale; import java.util.Map; -import java.util.ResourceBundle; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.spi.LocaleServiceProvider; import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.LocaleServiceProviderPool; @@ -112,9 +110,12 @@ * Use getInstance or getNumberInstance to get the * normal number format. Use getIntegerInstance to get an * integer number format. Use getCurrencyInstance to get the - * currency number format. And use getPercentInstance to get a - * format for displaying percentages. With this format, a fraction like - * 0.53 is displayed as 53%. + * currency number format. Use {@code getCompactNumberInstance} to get the + * compact number format to format a number in shorter form. For example, + * {@code 2000} can be formatted as {@code "2K"} in + * {@link java.util.Locale#US US locale}. Use getPercentInstance + * to get a format for displaying percentages. With this format, a fraction + * like 0.53 is displayed as 53%. * *

* You can also control the display of numbers with such methods as @@ -122,9 +123,10 @@ * If you want even more control over the format or parsing, * or want to give your users more control, * you can try casting the NumberFormat you get from the factory methods - * to a DecimalFormat. This will work for the vast majority - * of locales; just remember to put it in a try block in case you - * encounter an unusual one. + * to a {@code DecimalFormat} or {@code CompactNumberFormat} depending on + * the factory method used. This will work for the vast majority of locales; + * just remember to put it in a try block in case you encounter + * an unusual one. * *

* NumberFormat and DecimalFormat are designed such that some controls @@ -201,6 +203,7 @@ * * @see DecimalFormat * @see ChoiceFormat + * @see CompactNumberFormat * @author Mark Davis * @author Helena Shih * @since 1.1 @@ -472,7 +475,7 @@ * formatting */ public static final NumberFormat getInstance() { - return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); + return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE); } /** @@ -485,7 +488,7 @@ * formatting */ public static NumberFormat getInstance(Locale inLocale) { - return getInstance(inLocale, NUMBERSTYLE); + return getInstance(inLocale, null, NUMBERSTYLE); } /** @@ -501,7 +504,7 @@ * @see java.util.Locale.Category#FORMAT */ public static final NumberFormat getNumberInstance() { - return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); + return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE); } /** @@ -512,7 +515,7 @@ * formatting */ public static NumberFormat getNumberInstance(Locale inLocale) { - return getInstance(inLocale, NUMBERSTYLE); + return getInstance(inLocale, null, NUMBERSTYLE); } /** @@ -534,7 +537,7 @@ * @since 1.4 */ public static final NumberFormat getIntegerInstance() { - return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE); + return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, INTEGERSTYLE); } /** @@ -551,7 +554,7 @@ * @since 1.4 */ public static NumberFormat getIntegerInstance(Locale inLocale) { - return getInstance(inLocale, INTEGERSTYLE); + return getInstance(inLocale, null, INTEGERSTYLE); } /** @@ -566,7 +569,7 @@ * @see java.util.Locale.Category#FORMAT */ public static final NumberFormat getCurrencyInstance() { - return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE); + return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, CURRENCYSTYLE); } /** @@ -576,7 +579,7 @@ * @return the {@code NumberFormat} instance for currency formatting */ public static NumberFormat getCurrencyInstance(Locale inLocale) { - return getInstance(inLocale, CURRENCYSTYLE); + return getInstance(inLocale, null, CURRENCYSTYLE); } /** @@ -591,7 +594,7 @@ * @see java.util.Locale.Category#FORMAT */ public static final NumberFormat getPercentInstance() { - return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE); + return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, PERCENTSTYLE); } /** @@ -601,14 +604,14 @@ * @return the {@code NumberFormat} instance for percentage formatting */ public static NumberFormat getPercentInstance(Locale inLocale) { - return getInstance(inLocale, PERCENTSTYLE); + return getInstance(inLocale, null, PERCENTSTYLE); } /** * Returns a scientific format for the current default locale. */ /*public*/ final static NumberFormat getScientificInstance() { - return getInstance(Locale.getDefault(Locale.Category.FORMAT), SCIENTIFICSTYLE); + return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, SCIENTIFICSTYLE); } /** @@ -617,7 +620,50 @@ * @param inLocale the desired locale */ /*public*/ static NumberFormat getScientificInstance(Locale inLocale) { - return getInstance(inLocale, SCIENTIFICSTYLE); + return getInstance(inLocale, null, SCIENTIFICSTYLE); + } + + /** + * Returns a compact number format for the default + * {@link java.util.Locale.Category#FORMAT FORMAT} locale with + * {@link NumberFormat.Style#SHORT "SHORT"} format style. + * + * @return A {@code NumberFormat} instance for compact number + * formatting + * + * @see CompactNumberFormat + * @see NumberFormat.Style + * @see java.util.Locale#getDefault(java.util.Locale.Category) + * @see java.util.Locale.Category#FORMAT + * @since 12 + */ + public static NumberFormat getCompactNumberInstance() { + return getInstance(Locale.getDefault( + Locale.Category.FORMAT), NumberFormat.Style.SHORT, COMPACTSTYLE); + } + + /** + * Returns a compact number format for the specified {@link java.util.Locale locale} + * and {@link NumberFormat.Style formatStyle}. + * + * @param locale the desired locale + * @param formatStyle the style for formatting a number + * @return A {@code NumberFormat} instance for compact number + * formatting + * @throws NullPointerException if {@code locale} or {@code formatStyle} + * is {@code null} + * + * @see CompactNumberFormat + * @see NumberFormat.Style + * @see java.util.Locale + * @since 12 + */ + public static NumberFormat getCompactNumberInstance(Locale locale, + NumberFormat.Style formatStyle) { + + Objects.requireNonNull(locale); + Objects.requireNonNull(formatStyle); + return getInstance(locale, formatStyle, COMPACTSTYLE); } /** @@ -900,20 +946,22 @@ // =======================privates=============================== private static NumberFormat getInstance(Locale desiredLocale, - int choice) { + Style formatStyle, int choice) { LocaleProviderAdapter adapter; adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class, - desiredLocale); - NumberFormat numberFormat = getInstance(adapter, desiredLocale, choice); + desiredLocale); + NumberFormat numberFormat = getInstance(adapter, desiredLocale, + formatStyle, choice); if (numberFormat == null) { numberFormat = getInstance(LocaleProviderAdapter.forJRE(), - desiredLocale, choice); + desiredLocale, formatStyle, choice); } return numberFormat; } private static NumberFormat getInstance(LocaleProviderAdapter adapter, - Locale locale, int choice) { + Locale locale, Style formatStyle, + int choice) { NumberFormatProvider provider = adapter.getNumberFormatProvider(); NumberFormat numberFormat = null; switch (choice) { @@ -929,6 +977,9 @@ case INTEGERSTYLE: numberFormat = provider.getIntegerInstance(locale); break; + case COMPACTSTYLE: + numberFormat = provider.getCompactNumberInstance(locale, formatStyle); + break; } return numberFormat; } @@ -1001,6 +1052,7 @@ private static final int PERCENTSTYLE = 2; private static final int SCIENTIFICSTYLE = 3; private static final int INTEGERSTYLE = 4; + private static final int COMPACTSTYLE = 5; /** * True if the grouping (i.e. thousands) separator is used when @@ -1276,5 +1328,43 @@ * Constant identifying the exponent sign field. */ public static final Field EXPONENT_SIGN = new Field("exponent sign"); + + /** + * Constant identifying the prefix field. + * + * @since 12 + */ + public static final Field PREFIX = new Field("prefix"); + + /** + * Constant identifying the suffix field. + * + * @since 12 + */ + public static final Field SUFFIX = new Field("suffix"); + } + + /** + * A number format style. + *

+ * {@code Style} is an enum which represents the style for formatting + * a number within a given {@code NumberFormat} instance. + * + * @see CompactNumberFormat + * @see NumberFormat#getCompactNumberInstance(Locale, Style) + * @since 12 + */ + public enum Style { + + /** + * The {@code SHORT} number format style. + */ + SHORT, + + /** + * The {@code LONG} number format style. + */ + LONG + } } --- old/src/java.base/share/classes/java/text/spi/NumberFormatProvider.java 2018-11-28 14:09:06.858276887 +0530 +++ new/src/java.base/share/classes/java/text/spi/NumberFormatProvider.java 2018-11-28 14:09:06.566276887 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -110,4 +110,37 @@ * @see java.text.NumberFormat#getPercentInstance(java.util.Locale) */ public abstract NumberFormat getPercentInstance(Locale locale); + + /** + * Returns a new {@code NumberFormat} instance which formats + * a number in its compact form for the specified + * {@code locale} and {@code formatStyle}. + * + * @implSpec The default implementation of this method throws + * {@code UnSupportedOperationException}. Overriding the implementation + * of this method returns the compact number formatter instance + * of the given {@code locale} with specified {@code formatStyle}. + * + * @param locale the desired locale + * @param formatStyle the style for formatting a number + * @throws NullPointerException if {@code locale} or {@code formatStyle} + * is {@code null} + * @throws IllegalArgumentException if {@code locale} is not + * one of the locales returned from + * {@link java.util.spi.LocaleServiceProvider#getAvailableLocales() + * getAvailableLocales()}. + * @return a compact number formatter + * + * @see java.text.NumberFormat#getCompactNumberInstance(Locale, + * NumberFormat.Style) + * @since 12 + */ + public NumberFormat getCompactNumberInstance(Locale locale, + NumberFormat.Style formatStyle) { + throw new UnsupportedOperationException( + "The " + this.getClass().getName() + " should override this" + + " method to return compact number format instance of " + + locale + " locale and " + formatStyle + " style."); + } + } --- old/src/java.base/share/classes/sun/text/resources/FormatData.java 2018-11-28 14:09:07.654276887 +0530 +++ new/src/java.base/share/classes/sun/text/resources/FormatData.java 2018-11-28 14:09:07.350276887 +0530 @@ -796,6 +796,44 @@ "NaN", } }, + { "short.CompactNumberPatterns", + new String[] { + "", + "", + "", + "0K", + "00K", + "000K", + "0M", + "00M", + "000M", + "0B", + "00B", + "000B", + "0T", + "00T", + "000T", + } + }, + { "long.CompactNumberPatterns", + new String[] { + "", + "", + "", + "0 thousand", + "00 thousand", + "000 thousand", + "0 million", + "00 million", + "000 million", + "0 billion", + "00 billion", + "000 billion", + "0 trillion", + "00 trillion", + "000 trillion", + } + }, { "TimePatterns", new String[] { "h:mm:ss a z", // full time pattern --- old/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java 2018-11-28 14:09:08.410276887 +0530 +++ new/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java 2018-11-28 14:09:08.122276887 +0530 @@ -43,6 +43,7 @@ import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.text.MessageFormat; +import java.text.NumberFormat; import java.util.Calendar; import java.util.HashSet; import java.util.LinkedHashSet; @@ -88,6 +89,7 @@ private static final String ZONE_IDS_CACHEKEY = "ZID"; private static final String CALENDAR_NAMES = "CALN."; private static final String NUMBER_PATTERNS_CACHEKEY = "NP"; + private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP"; private static final String DATE_TIME_PATTERN = "DTP."; // TimeZoneNamesBundle exemplar city prefix @@ -477,6 +479,32 @@ return numberPatterns; } + + /** + * Returns the compact number format patterns. + * @param formatStyle the style for formatting a number + * @return an array of compact number patterns + */ + @SuppressWarnings("unchecked") + public String[] getCNPatterns(NumberFormat.Style formatStyle) { + + Objects.requireNonNull(formatStyle); + String[] compactNumberPatterns = null; + removeEmptyReferences(); + String width = (formatStyle == NumberFormat.Style.LONG) ? "long" : "short"; + String cacheKey = width + "." + COMPACT_NUMBER_PATTERNS_CACHEKEY; + ResourceReference data = cache.get(cacheKey); + if (data == null || ((compactNumberPatterns + = (String[]) data.get()) == null)) { + ResourceBundle resource = localeData.getNumberFormatData(locale); + compactNumberPatterns = (String[]) resource + .getObject(width + ".CompactNumberPatterns"); + cache.put(cacheKey, new ResourceReference(cacheKey, + (Object) compactNumberPatterns, referenceQueue)); + } + return compactNumberPatterns; + } + /** * Returns the FormatData resource bundle of this LocaleResources. --- old/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java 2018-11-28 14:09:09.190276887 +0530 +++ new/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java 2018-11-28 14:09:08.878276887 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,12 +40,14 @@ package sun.util.locale.provider; +import java.text.CompactNumberFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.spi.NumberFormatProvider; import java.util.Currency; import java.util.Locale; +import java.util.Objects; import java.util.Set; /** @@ -224,6 +226,49 @@ } } } + + /** + * Returns a new {@code NumberFormat} instance which formats + * a number in its compact form for the specified + * {@code locale} and {@code formatStyle}. + * + * @param locale the desired locale + * @param formatStyle the style for formatting a number + * @throws NullPointerException if {@code locale} or {@code formatStyle} + * is {@code null} + * @throws IllegalArgumentException if {@code locale} isn't + * one of the locales returned from + * {@link java.util.spi.LocaleServiceProvider#getAvailableLocales() + * getAvailableLocales()}. + * @return a compact number formatter + * + * @see java.text.NumberFormat#getCompactNumberInstance(Locale, + * NumberFormat.Style) + * @since 12 + */ + @Override + public NumberFormat getCompactNumberInstance(Locale locale, + NumberFormat.Style formatStyle) { + + Objects.requireNonNull(locale); + Objects.requireNonNull(formatStyle); + + // Check for region override + Locale override = locale.getUnicodeLocaleType("nu") == null + ? CalendarDataUtility.findRegionOverride(locale) + : locale; + + LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type); + LocaleResources resource = adapter.getLocaleResources(override); + + String[] numberPatterns = resource.getNumberPatterns(); + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(override); + String[] cnPatterns = resource.getCNPatterns(formatStyle); + + CompactNumberFormat format = new CompactNumberFormat(numberPatterns[0], + symbols, cnPatterns); + return format; + } @Override public Set getAvailableLanguageTags() { --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/make/jdk/src/classes/build/tools/cldrconverter/StringListElement.java 2018-11-28 14:09:09.634276887 +0530 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package build.tools.cldrconverter; + +class StringListElement extends Container { + + StringListEntry list; + int index; + + StringListElement(String qName, Container parent, int index) { + super(qName, parent); + while (!(parent instanceof StringListEntry)) { + parent = parent.getParent(); + } + list = (StringListEntry) parent; + this.index = index; + } + + @Override + void addCharacters(char[] characters, int start, int length) { + list.addCharacters(index, characters, start, length); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/make/jdk/src/classes/build/tools/cldrconverter/StringListEntry.java 2018-11-28 14:09:10.414276887 +0530 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package build.tools.cldrconverter; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +class StringListEntry extends Entry> { + + private List value; + + StringListEntry(String qName, Container parent, String key) { + super(qName, parent, key); + value = new ArrayList<>(); + } + + void addCharacters(int index, char[] characters, int start, int length) { + // fill with empty strings when the patterns start from index > 0 + if (value.size() < index) { + IntStream.range(0, index).forEach(i -> value.add(i, "")); + value.add(index, new String(characters, start, length)); + } else { + value.add(index, new String(characters, start, length)); + } + } + + @Override + List getValue() { + for (String element : value) { + if (element != null) { + return value; + } + } + return null; + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/src/java.base/share/classes/java/text/CompactNumberFormat.java 2018-11-28 14:09:11.174276887 +0530 @@ -0,0 +1,2113 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + + +/** + *

+ * {@code CompactNumberFormat} is a concrete subclass of {@code NumberFormat} + * that formats a decimal number in its compact form. + * + * The compact number formatting is designed for the environment where the space + * is limited, and the formatted string can be displayed in that limited space. + * It is defined by LDML's specification for + * + * Compact Number Formats. A compact number formatting refers + * to the representation of a number in a shorter form, based on the patterns + * provided for a given locale. + * + *

+ * For example: + *
In the {@link java.util.Locale#US US locale}, {@code 1000} can be formatted + * as {@code "1K"}, and {@code 1000000} as {@code "1M"}, depending upon the + * style used. + *
In the {@code "hi_IN"} locale, {@code 1000} can be formatted as + * "1 \u0939\u091C\u093C\u093E\u0930", and {@code 50000000} as "5 \u0915.", + * depending upon the style used. + * + *

+ * To obtain a {@code CompactNumberFormat} for a locale, use one + * of the factory methods given by {@code NumberFormat} for compact number + * formatting. For example, + * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. + * + *

+ * NumberFormat fmt = NumberFormat.getCompactNumberInstance(
+ *                             new Locale("hi", "IN"), NumberFormat.Style.SHORT);
+ * String result = fmt.format(1000);
+ * 
+ * + *

Style

+ *

+ * A number can be formatted in the compact forms with two different + * styles, {@link NumberFormat.Style#SHORT SHORT} + * and {@link NumberFormat.Style#LONG LONG}. Use + * {@link NumberFormat#getCompactNumberInstance(Locale, Style)} for formatting and + * parsing a number in {@link NumberFormat.Style#SHORT SHORT} or + * {@link NumberFormat.Style#LONG LONG} compact form, + * where the given {@code Style} parameter requests the desired + * format. A {@link NumberFormat.Style#SHORT SHORT} style + * compact number instance in the {@link java.util.Locale#US US locale} formats + * {@code 10000} as {@code "10K"}. However, a + * {@link NumberFormat.Style#LONG LONG} style instance in same locale + * formats {@code 10000} as {@code "10 thousand"}. + * + *

Compact Number Patterns

+ *

+ * The compact number patterns are represented in a series of patterns where each + * pattern is used to format a range of numbers. An example of + * {@link NumberFormat.Style#SHORT SHORT} styled compact number patterns + * for the {@link java.util.Locale#US US locale} is {@code {"", "", "", "0K", + * "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"}}, + * ranging from {@code 10}{@code 0} to {@code 10}{@code 14}. + * There can be any number of patterns and they are + * strictly index based starting from the range {@code 10}{@code 0}. + * For example, in the above patterns, pattern at index 3 + * ({@code "0K"}) is used for formatting {@code number >= 1000 and number < 10000}, + * pattern at index 4 ({@code "00K"}) is used for formatting + * {@code number >= 10000 and number < 100000} and so on. In most of the locales, + * patterns with the range + * {@code 10}{@code 0}-{@code 10}{@code 2} are empty + * strings, which implicitly means a special pattern {@code "0"}. + * A special pattern {@code "0"} is used for any range which does not contain + * a compact pattern. This special pattern can appear explicitly for any specific + * range, or considered as a default pattern for an empty string. + *

+ * A compact pattern has the following syntax: + *

+ * Pattern:
+ *         PositivePattern
+ *         PositivePattern [; NegativePattern]optional
+ * PositivePattern:
+ *         Prefixoptional MinimumInteger Suffixoptional
+ * NegativePattern:
+ *        Prefixoptional MinimumInteger Suffixoptional
+ * Prefix:
+ *      Any Unicode characters except \uFFFE, \uFFFF, and
+ *      special characters
+ * Suffix:
+ *      Any Unicode characters except \uFFFE, \uFFFF, and
+ *      special characters
+ * MinimumInteger:
+ *      0
+ *      0 MinimumInteger
+ * 
+ * + * A compact pattern contains a positive and negative subpattern + * separated by a subpattern boundary character {@code ';' (U+003B)}, + * for example, {@code "0K;-0K"}. Each subpattern has a prefix, + * minimum integer digits, and suffix. The negative subpattern + * is optional, if absent, then the positive subpattern prefixed with the + * minus sign ({@code '-' U+002D HYPHEN-MINUS}) is used as the negative + * subpattern. That is, {@code "0K"} alone is equivalent to {@code "0K;-0K"}. + * If there is an explicit negative subpattern, it serves only to specify + * the negative prefix and suffix. The number of minimum integer digits, + * and other characteristics are all the same as the positive pattern. + * That means that {@code "0K;-00K"} produces precisely the same behavior + * as {@code "0K;-0K"}. + * + *

+ * Many characters in a compact pattern are taken literally, they are matched + * during parsing and output unchanged during formatting. + * Special characters, + * on the other hand, stand for other characters, strings, or classes of + * characters. They must be quoted, using single quote {@code ' (U+0027)} + * unless noted otherwise, if they are to appear in the prefix or suffix + * as literals. For example, 0\u0915'.'. + * + *

Formatting

+ * The default formatting behavior returns a formatted string with no fractional + * digits, however users can use the {@link #setMinimumFractionDigits(int)} + * method to include the fractional part. + * The number {@code 1000.0} or {@code 1000} is formatted as {@code "1K"} + * not {@code "1.00K"} (in the {@link java.util.Locale#US US locale}). For this + * reason, the patterns provided for formatting contain only the minimum + * integer digits, prefix and/or suffix, but no fractional part. + * For example, patterns used are {@code {"", "", "", 0K, 00K, ...}}. If the pattern + * selected for formatting a number is {@code "0"} (special pattern), + * either explicit or defaulted, then the general number formatting provided by + * {@link java.text.DecimalFormat DecimalFormat} + * for the specified locale is used. + * + *

Parsing

+ * The default parsing behavior does not allow a grouping separator until + * grouping used is set to {@code true} by using + * {@link #setGroupingUsed(boolean)}. The parsing of the fractional part + * depends on the {@link #isParseIntegerOnly()}. For example, if the + * parse integer only is set to true, then the fractional part is skipped. + * + *

Rounding

+ * {@code CompactNumberFormat} provides rounding modes defined in + * {@link java.math.RoundingMode} for formatting. By default, it uses + * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. + * + * @see CompactNumberFormat.Style + * @see NumberFormat + * @see DecimalFormat + * @since 12 + */ +public final class CompactNumberFormat extends NumberFormat { + + private static final long serialVersionUID = 7128367218649234678L; + + /** + * The patterns for compact form of numbers for this + * {@code CompactNumberFormat}. A possible example is + * {@code {"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B", + * "00B", "000B", "0T", "00T", "000T"}} ranging from + * {@code 10}{@code 0}-{@code 10}{@code 14}, + * where each pattern is used to format a range of numbers. + * For example, {@code "0K"} is used for formatting + * {@code number >= 1000 and number < 10000}, {@code "00K"} is used for + * formatting {@code number >= 10000 and number < 100000} and so on. + * This field must not be {@code null}. + * + * @serial + */ + private String[] compactPatterns; + + /** + * List of positive prefix patterns of this formatter's + * compact number patterns. + */ + private transient List positivePrefixPatterns; + + /** + * List of negative prefix patterns of this formatter's + * compact number patterns. + */ + private transient List negativePrefixPatterns; + + /** + * List of positive suffix patterns of this formatter's + * compact number patterns. + */ + private transient List positiveSuffixPatterns; + + /** + * List of negative suffix patterns of this formatter's + * compact number patterns. + */ + private transient List negativeSuffixPatterns; + + /** + * List of divisors of this formatter's compact number patterns. + * Divisor can be either Long or BigInteger (if the divisor value goes + * beyond long boundary) + */ + private transient List divisors; + + /** + * 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}. + * + * @serial + * @see DecimalFormatSymbols + */ + private DecimalFormatSymbols symbols; + + /** + * The decimal pattern which is used for formatting the numbers + * matching special pattern "0". This field must not be {@code null}. + * + * @serial + * @see DecimalFormat + */ + private final String decimalPattern; + + /** + * A {@code DecimalFormat} used by this format for getting corresponding + * general number formatting behavior for compact numbers. + * + */ + private transient DecimalFormat decimalFormat; + + /** + * A {@code DecimalFormat} used by this format for getting general number + * formatting behavior for the numbers which can't be represented as compact + * numbers. For example, number matching the special pattern "0" are + * formatted through general number format pattern provided by + * {@link java.text.DecimalFormat DecimalFormat} + * for the specified locale. + * + */ + private transient DecimalFormat defaultDecimalFormat; + + /** + * The number of digits between grouping separators in the integer portion + * of a compact number. For the grouping to work while formatting, this + * field needs to be greater than 0 with grouping used set as true. + * This field must not be negative. + * + * @serial + */ + private byte groupingSize = 0; + + /** + * Returns whether the {@link #parse(String, ParsePosition)} + * method returns {@code BigDecimal}. + * + * @serial + */ + private boolean parseBigDecimal = false; + + /** + * The {@code RoundingMode} used in this compact number format. + * This field must not be {@code null}. + * + * @serial + */ + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + + /** + * Special pattern used for compact numbers + */ + private static final String SPECIAL_PATTERN = "0"; + + /** + * Multiplier for compact pattern range. In + * the list compact patterns each compact pattern + * specify the range with the multiplication factor of 10 + * of its previous compact pattern range. + * For example, 10^0, 10^1, 10^2, 10^3, 10^4... + * + */ + private static final int RANGE_MULTIPLIER = 10; + + /** + * Creates a {@code CompactNumberFormat} using the given decimal pattern, + * decimal format symbols and compact patterns. + * To obtain the instance of {@code CompactNumberFormat} with the standard + * compact patterns for a {@code Locale} and {@code Style}, + * it is recommended to use the factory methods given by + * {@code NumberFormat} for compact number formatting. For example, + * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}. + * + * @param decimalPattern a decimal pattern for general number formatting + * @param symbols the set of symbols to be used + * @param compactPatterns an array of + * + * compact number patterns + * @throws NullPointerException if any of the given arguments is + * {@code null} + * @throws IllegalArgumentException if the given {@code decimalPattern} or the + * {@code compactPatterns} array contains an invalid pattern + * or if a {@code null} appears in the array of compact + * patterns + * @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols) + * @see DecimalFormatSymbols + */ + public CompactNumberFormat(String decimalPattern, + DecimalFormatSymbols symbols, String[] compactPatterns) { + + Objects.requireNonNull(decimalPattern, "decimalPattern"); + Objects.requireNonNull(symbols, "symbols"); + Objects.requireNonNull(compactPatterns, "compactPatterns"); + + this.symbols = symbols; + // instantiating the DecimalFormat with "0" pattern; this acts just as a + // basic pattern; the properties (For example, prefix/suffix) + // are later computed based on the compact number formatting process. + decimalFormat = new DecimalFormat(SPECIAL_PATTERN, this.symbols); + + // Initializing the super class state with the decimalFormat values + // to represent this CompactNumberFormat. + + // For setting the digits counts, use overridden setXXX methods of this + // CompactNumberFormat, as it performs check with the max range allowed + // for compact number formatting + setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); + setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); + setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); + setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); + + super.setGroupingUsed(decimalFormat.isGroupingUsed()); + super.setParseIntegerOnly(decimalFormat.isParseIntegerOnly()); + + this.compactPatterns = compactPatterns; + + // DecimalFormat used for formatting numbers with special pattern "0". + // Formatting is delegated to the DecimalFormat's number formatting + // with no fraction digits + this.decimalPattern = decimalPattern; + defaultDecimalFormat = new DecimalFormat(this.decimalPattern, + this.symbols); + defaultDecimalFormat.setMaximumFractionDigits(0); + // process compact patterns to extract the prefixes, suffixes and + // divisors + processCompactPatterns(); + } + + /** + * Formats a number to produce a string representing its compact form. + * The number can be of any subclass of {@link java.lang.Number}. + * @param number the number to format + * @param toAppendTo the {@code StringBuffer} to which the formatted + * text is to be appended + * @param fieldPosition keeps track on the position of the field within + * the returned string. For example, for formatting + * a number {@code 123456789} in the + * {@link java.util.Locale#US US locale}, + * if the given {@code fieldPosition} is + * {@link NumberFormat#INTEGER_FIELD}, the begin + * index and end index of {@code fieldPosition} + * will be set to 0 and 3, respectively for the + * output string {@code 123M}. Similarly, positions + * of the prefix and the suffix fields can be + * obtained using {@link NumberFormat.Field#PREFIX} + * and {@link NumberFormat.Field#SUFFIX} respectively. + * @return the {@code StringBuffer} passed in as {@code toAppendTo} + * @throws IllegalArgumentException if {@code number} is + * {@code null} or not an instance of {@code Number} + * @throws NullPointerException if {@code toAppendTo} or + * {@code fieldPosition} is {@code null} + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to {@code RoundingMode.UNNECESSARY} + * @see FieldPosition + */ + @Override + public final StringBuffer format(Object number, + StringBuffer toAppendTo, + FieldPosition fieldPosition) { + if (number instanceof Long || number instanceof Integer + || number instanceof Short || number instanceof Byte + || number instanceof AtomicInteger + || number instanceof AtomicLong + || (number instanceof BigInteger + && ((BigInteger) number).bitLength() < 64)) { + return format(((Number) number).longValue(), toAppendTo, + fieldPosition); + } else if (number instanceof BigDecimal) { + return format((BigDecimal) number, toAppendTo, fieldPosition); + } else if (number instanceof BigInteger) { + return format((BigInteger) number, toAppendTo, fieldPosition); + } else if (number instanceof Number) { + return format(((Number) number).doubleValue(), toAppendTo, fieldPosition); + } else { + throw new IllegalArgumentException("Cannot format " + + number.getClass().getName() + " as a number"); + } + } + + /** + * Formats a double to produce a string representing its compact form. + * @param number the double number to format + * @param result where the text is to be appended + * @param fieldPosition keeps track on the position of the field within + * the returned string. For example, to format + * a number {@code 1234567.89} in the + * {@link java.util.Locale#US US locale} + * if the given {@code fieldPosition} is + * {@link NumberFormat#INTEGER_FIELD}, the begin + * index and end index of {@code fieldPosition} + * will be set to 0 and 1, respectively for the + * output string {@code 1M}. Similarly, positions + * of the prefix and the suffix fields can be + * obtained using {@link NumberFormat.Field#PREFIX} + * and {@link NumberFormat.Field#SUFFIX} respectively. + * @return the {@code StringBuffer} passed in as {@code result} + * @throws NullPointerException if {@code result} or + * {@code fieldPosition} is {@code null} + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to {@code RoundingMode.UNNECESSARY} + * @see FieldPosition + */ + @Override + public StringBuffer format(double number, StringBuffer result, + FieldPosition fieldPosition) { + + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); + } + + private StringBuffer format(double number, StringBuffer result, + FieldDelegate delegate) { + + boolean nanOrInfinity = decimalFormat.handleNaN(number, result, delegate); + if (nanOrInfinity) { + return result; + } + + boolean isNegative = ((number < 0.0) + || (number == 0.0 && 1 / number < 0.0)); + + nanOrInfinity = decimalFormat.handleInfinity(number, result, delegate, isNegative); + if (nanOrInfinity) { + return result; + } + + // round the double value with min fraction digits, the integer + // part of the rounded value is used for matching the compact + // number pattern + // For example, if roundingMode is HALF_UP with min fraction + // digits = 0, the number 999.6 should round up + // to 1000 and outputs 1K/thousand in "en_US" locale + DigitList dList = new DigitList(); + dList.setRoundingMode(getRoundingMode()); + number = isNegative ? -number : number; + dList.set(isNegative, number, getMinimumFractionDigits()); + + double roundedNumber = dList.getDouble(); + int compactDataIndex = selectCompactPattern((long) roundedNumber); + if (compactDataIndex != -1) { + String prefix = isNegative ? negativePrefixPatterns.get(compactDataIndex) + : positivePrefixPatterns.get(compactDataIndex); + String suffix = isNegative ? negativeSuffixPatterns.get(compactDataIndex) + : positiveSuffixPatterns.get(compactDataIndex); + + if (!prefix.isEmpty() || !suffix.isEmpty()) { + appendPrefix(result, prefix, delegate); + long divisor = (Long) divisors.get(compactDataIndex); + 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); + } + return result; + } + + /** + * Formats a long to produce a string representing its compact form. + * @param number the long number to format + * @param result where the text is to be appended + * @param fieldPosition keeps track on the position of the field within + * the returned string. For example, to format + * a number {@code 123456789} in the + * {@link java.util.Locale#US US locale}, + * if the given {@code fieldPosition} is + * {@link NumberFormat#INTEGER_FIELD}, the begin + * index and end index of {@code fieldPosition} + * will be set to 0 and 3, respectively for the + * output string {@code 123M}. Similarly, positions + * of the prefix and the suffix fields can be + * obtained using {@link NumberFormat.Field#PREFIX} + * and {@link NumberFormat.Field#SUFFIX} respectively. + * @return the {@code StringBuffer} passed in as {@code result} + * @throws NullPointerException if {@code result} or + * {@code fieldPosition} is {@code null} + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to {@code RoundingMode.UNNECESSARY} + * @see FieldPosition + */ + @Override + public StringBuffer format(long number, StringBuffer result, + FieldPosition fieldPosition) { + + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); + } + + private StringBuffer format(long number, StringBuffer result, FieldDelegate delegate) { + boolean isNegative = (number < 0); + if (isNegative) { + number = -number; + } + + if (number < 0) { // LONG_MIN + BigInteger bigIntegerValue = BigInteger.valueOf(number); + return format(bigIntegerValue, result, delegate, true); + } + + int compactDataIndex = selectCompactPattern(number); + if (compactDataIndex != -1) { + String prefix = isNegative ? negativePrefixPatterns.get(compactDataIndex) + : positivePrefixPatterns.get(compactDataIndex); + String suffix = isNegative ? negativeSuffixPatterns.get(compactDataIndex) + : positiveSuffixPatterns.get(compactDataIndex); + if (!prefix.isEmpty() || !suffix.isEmpty()) { + appendPrefix(result, prefix, delegate); + long divisor = (Long) divisors.get(compactDataIndex); + if ((number % divisor == 0)) { + number = number / divisor; + decimalFormat.setDigitList(number, isNegative, 0); + decimalFormat.subformatNumber(result, delegate, + isNegative, true, getMaximumIntegerDigits(), + getMinimumIntegerDigits(), getMaximumFractionDigits(), + getMinimumFractionDigits()); + } else { + // to avoid truncation of fractional part store + // the value in double and follow double path instead of + // long path + double dNumber = (double) number / divisor; + decimalFormat.setDigitList(dNumber, isNegative, getMaximumFractionDigits()); + decimalFormat.subformatNumber(result, delegate, + isNegative, false, getMaximumIntegerDigits(), + getMinimumIntegerDigits(), getMaximumFractionDigits(), + getMinimumFractionDigits()); + } + appendSuffix(result, suffix, delegate); + } else { + number = isNegative ? -number : number; + defaultDecimalFormat.format(number, result, delegate); + } + } else { + number = isNegative ? -number : number; + defaultDecimalFormat.format(number, result, delegate); + } + return result; + } + + /** + * Formats a BigDecimal to produce a string representing its compact form. + * @param number the BigDecimal number to format + * @param result where the text is to be appended + * @param fieldPosition keeps track on the position of the field within + * the returned string. For example, to format + * a number {@code 1234567.89} in the + * {@link java.util.Locale#US US locale}, + * if the given {@code fieldPosition} is + * {@link NumberFormat#INTEGER_FIELD}, the begin + * index and end index of {@code fieldPosition} + * will be set to 0 and 1, respectively for the + * output string {@code 1M}. Similarly, positions + * of the prefix and the suffix fields can be + * obtained using {@link NumberFormat.Field#PREFIX} + * and {@link NumberFormat.Field#SUFFIX} respectively. + * @return the {@code StringBuffer} passed in as {@code result} + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to {@code RoundingMode.UNNECESSARY} + * @throws NullPointerException if any of the given parameter + * is {@code null} + * @see FieldPosition + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldPosition fieldPosition) { + + Objects.requireNonNull(number); + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); + + } + + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldDelegate delegate) { + + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + // round the value with min fraction digits, the integer + // part of the rounded value is used for matching the compact + // number pattern + // e.g. If roundingMode is HALF_UP with min fraction digits = 0, + // the number 999.6 should round up + // to 1000 and outputs 1K/thousand in "en_US" locale + number = number.setScale(getMinimumFractionDigits(), getRoundingMode()); + + int compactDataIndex; + if (number.toBigInteger().bitLength() < 64) { + compactDataIndex = selectCompactPattern(number.toBigInteger().longValue()); + } else { + compactDataIndex = selectCompactPattern(number.toBigInteger()); + } + + if (compactDataIndex != -1) { + String prefix = isNegative ? negativePrefixPatterns.get(compactDataIndex) + : positivePrefixPatterns.get(compactDataIndex); + String suffix = isNegative ? negativeSuffixPatterns.get(compactDataIndex) + : positiveSuffixPatterns.get(compactDataIndex); + if (!prefix.isEmpty() || !suffix.isEmpty()) { + appendPrefix(result, prefix, delegate); + Number divisor = divisors.get(compactDataIndex); + number = number.divide(new BigDecimal("" + divisor)); + 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 { + number = isNegative ? number.negate() : number; + defaultDecimalFormat.format(number, result, delegate); + } + return result; + } + + /** + * Formats a BigInteger to produce a string representing its compact form. + * @param number the BigInteger number to format + * @param result where the text is to be appended + * @param fieldPosition keeps track on the position of the field within + * the returned string. For example, to format + * a number {@code 123456789} in the + * {@link java.util.Locale#US US locale}, + * if the given {@code fieldPosition} is + * {@link NumberFormat#INTEGER_FIELD}, the begin index + * and end index of {@code fieldPosition} will be set + * to 0 and 3, respectively for the output string + * {@code 123M}. Similarly, positions of the + * prefix and the suffix fields can be obtained + * using {@link NumberFormat.Field#PREFIX} and + * {@link NumberFormat.Field#SUFFIX} respectively. + * @return the {@code StringBuffer} passed in as {@code result} + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to {@code RoundingMode.UNNECESSARY} + * @throws NullPointerException if any of the given parameter + * is {@code null} + * @see FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldPosition fieldPosition) { + + Objects.requireNonNull(number); + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate(), false); + } + + private StringBuffer format(BigInteger number, StringBuffer result, + FieldDelegate delegate, boolean formatLong) { + + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + int compactDataIndex = selectCompactPattern(number); + if (compactDataIndex != -1) { + String prefix = isNegative ? negativePrefixPatterns.get(compactDataIndex) + : positivePrefixPatterns.get(compactDataIndex); + String suffix = isNegative ? negativeSuffixPatterns.get(compactDataIndex) + : positiveSuffixPatterns.get(compactDataIndex); + if (!prefix.isEmpty() || !suffix.isEmpty()) { + appendPrefix(result, prefix, delegate); + Number divisor = divisors.get(compactDataIndex); + if (number.mod(new BigInteger(divisor.toString())) + .compareTo(BigInteger.ZERO) == 0) { + number = number.divide(new BigInteger(divisor.toString())); + + decimalFormat.setDigitList(number, isNegative, 0); + decimalFormat.subformatNumber(result, delegate, + isNegative, true, getMaximumIntegerDigits(), + getMinimumIntegerDigits(), getMaximumFractionDigits(), + getMinimumFractionDigits()); + } else { + // to avoid truncation of fractional part store the value in + // BigDecimal and follow BigDecimal path instead of + // BigInteger path + BigDecimal nDecimal = new BigDecimal(number) + .divide(new BigDecimal(divisor.toString())); + decimalFormat.setDigitList(nDecimal, 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, formatLong); + } + } else { + number = isNegative ? number.negate() : number; + defaultDecimalFormat.format(number, result, delegate, formatLong); + } + return result; + } + + /** + * Appends the {@code prefix} to the {@code result} and also set the + * {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.PREFIX} + * field positions. + * @param result the resulting string, where the pefix is to be appended + * @param prefix prefix to append + * @param delegate notified of the locations of + * {@code NumberFormat.Field.SIGN} and + * {@code NumberFormat.Field.PREFIX} fields + */ + private void appendPrefix(StringBuffer result, String prefix, + FieldDelegate delegate) { + append(result, expandAffix(prefix), delegate, + getFieldPositions(prefix, NumberFormat.Field.PREFIX)); + } + + /** + * Appends {@code suffix} to the {@code result} and also set the + * {@code NumberFormat.Field.SIGN} and {@code NumberFormat.Field.SUFFIX} + * field positions. + * @param result the resulting string, where the suffix is to be appended + * @param prefix suffix to append + * @param delegate notified of the locations of + * {@code NumberFormat.Field.SIGN} and + * {@code NumberFormat.Field.SUFFIX} fields + */ + private void appendSuffix(StringBuffer result, String suffix, + FieldDelegate delegate) { + append(result, expandAffix(suffix), delegate, + getFieldPositions(suffix, NumberFormat.Field.SUFFIX)); + } + + /** + * Appends the {@code string} to the {@code result}. + * {@code delegate} is notified of SIGN, PREFIX and/or SUFFIX + * field positions. + * @param result the resulting string, where the text is to be appended + * @param string the text to append + * @param delegate notified of the locations of sub fields + * @param positions a list of {@code FieldPostion} in the given + * string + */ + private void append(StringBuffer result, String string, + FieldDelegate delegate, List positions) { + int start = result.length(); + if (string.length() > 0) { + 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); + } + } + } + + /** + * Expands an affix {@code pattern} into a string of literals. + * All characters in the pattern are literals unless prefixed by QUOTE. + * The character prefixed by QUOTE is replaced with its respective + * localized literal. + * @param pattern a compact number pattern affix + * @return an expanded affix + */ + private String expandAffix(String pattern) { + StringBuilder sb = new StringBuilder(); + for (int index = 0; index < pattern.length();) { + char ch = pattern.charAt(index++); + if (ch == QUOTE) { + ch = pattern.charAt(index++); + if (ch == MINUS_SIGN) { + ch = symbols.getMinusSign(); + } + } + sb.append(ch); + } + return sb.toString(); + } + + /** + * Returns a list of {@code FieldPostion} in the given {@code pattern}. + * @param pattern the pattern to be parsed for {@code FieldPosition} + * @param field whether a PREFIX or SUFFIX field + * @return a list of {@code FieldPostion} + */ + private List getFieldPositions(String pattern, Field field) { + List positions = new ArrayList<>(); + StringBuilder affix = new StringBuilder(); + int stringIndex = 0; + for (int index = 0; index < pattern.length();) { + char ch = pattern.charAt(index++); + if (ch == QUOTE) { + ch = pattern.charAt(index++); + if (ch == MINUS_SIGN) { + ch = symbols.getMinusSign(); + FieldPosition fp = new FieldPosition(NumberFormat.Field.SIGN); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + 1); + positions.add(fp); + } + } + stringIndex++; + affix.append(ch); + } + if (affix.length() != 0) { + FieldPosition fp = new FieldPosition(field); + fp.setBeginIndex(0); + fp.setEndIndex(affix.length()); + positions.add(fp); + } + return positions; + } + + /** + * Select the index of the matched compact number pattern for + * the given {@code long} {@code number}. + * + * @param number number to be formatted + * @return index of matched compact pattern; + * -1 if no compact patterns specified + */ + private int selectCompactPattern(long number) { + + if (compactPatterns.length == 0) { + return -1; + } + + // minimum index can be "0", max index can be "size - 1" + int dataIndex = number <= 1 ? 0 : (int) Math.log10(number); + dataIndex = Math.min(dataIndex, compactPatterns.length - 1); + return dataIndex; + } + + /** + * Select the index of the matched compact number + * pattern for the given {@code BigInteger} {@code number}. + * + * @param number number to be formatted + * @return index of matched compact pattern; + * -1 if no compact patterns specified + */ + private int selectCompactPattern(BigInteger number) { + + int matchedIndex = -1; + if (compactPatterns.length == 0) { + return matchedIndex; + } + + BigInteger currentValue = BigInteger.ONE; + + // For formatting a number, the greatest type less than + // or equal to number is used + for (int index = 0; index < compactPatterns.length; index++) { + if (number.compareTo(currentValue) == 0) { + // equal + matchedIndex = index; + break; + } else if (number.compareTo(currentValue) > 0) { + // input number is greater than current type; try matching with + // the next + matchedIndex = index; + currentValue = currentValue.multiply(BigInteger.valueOf(RANGE_MULTIPLIER)); + } else { + // type is greater than the input number; take the previous + // pattern + break; + } + } + return matchedIndex; + } + + /** + * Formats an Object producing an {@code AttributedCharacterIterator}. + * The returned {@code AttributedCharacterIterator} can be used + * to build the resulting string, as well as to determine information + * about the resulting string. + *

+ * Each attribute key of the {@code AttributedCharacterIterator} will + * be of type {@code NumberFormat.Field}, with the attribute value + * being the same as the attribute key. The prefix and the suffix + * parts of the returned iterator (if present) are represented by + * the attributes {@link NumberFormat.Field#PREFIX} and + * {@link NumberFormat.Field#SUFFIX} respectively. + * + * + * @throws NullPointerException if obj is null + * @throws IllegalArgumentException when the Format cannot format the + * given object + * @throws ArithmeticException if rounding is needed with rounding + * mode being set to {@code RoundingMode.UNNECESSARY} + * @param obj The object to format + * @return an {@code AttributedCharacterIterator} describing the + * formatted value + */ + @Override + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + CharacterIteratorFieldDelegate delegate + = new CharacterIteratorFieldDelegate(); + StringBuffer sb = new StringBuffer(); + + if (obj instanceof Double || obj instanceof Float) { + format(((Number) obj).doubleValue(), sb, delegate); + } else if (obj instanceof Long || obj instanceof Integer + || obj instanceof Short || obj instanceof Byte + || obj instanceof AtomicInteger || obj instanceof AtomicLong) { + format(((Number) obj).longValue(), sb, delegate); + } else if (obj instanceof BigDecimal) { + format((BigDecimal) obj, sb, delegate); + } else if (obj instanceof BigInteger) { + format((BigInteger) obj, sb, delegate, false); + } else if (obj == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } else { + throw new IllegalArgumentException( + "Cannot format given Object as a Number"); + } + return delegate.getIterator(sb.toString()); + } + + /** + * Computes the divisor using minimum integer digits and + * matched pattern index. + * @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; + 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; + } + + /** + * Process the series of compact patterns to compute the + * series of prefixes, suffixes and their respective divisor + * value. + * + */ + 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++) { + applyPattern(compactPatterns[index], index); + } + } + + /** + * Process a compact pattern at a specific {@code index} + * @param pattern the compact pattern to be processed + * @param index index in the array of compact patterns + * + */ + private void applyPattern(String pattern, int index) { + int start = 0; + + boolean gotNegative = false; + + String positivePrefix = ""; + String positiveSuffix = ""; + String negativePrefix = ""; + String negativeSuffix = ""; + String zeros = ""; + for (int j = 1; j >= 0 && start < pattern.length(); --j) { + + StringBuffer prefix = new StringBuffer(); + StringBuffer suffix = new StringBuffer(); + boolean inQuote = false; + // The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is + // the section of the pattern with digits. Phase 2 is the suffix. + // The separation of the characters into phases is + // strictly enforced; if phase 1 characters are to appear in the + // suffix, for example, they must be quoted. + int phase = 0; + + // The affix is either the prefix or the suffix. + StringBuffer affix = prefix; + + for (int pos = start; pos < pattern.length(); ++pos) { + char ch = pattern.charAt(pos); + switch (phase) { + case 0: + case 2: + // Process the prefix / suffix characters + if (inQuote) { + // A quote within quotes indicates either the closing + // quote or two quotes, which is a quote literal. That + // is, we have the second quote in 'do' or 'don''t'. + if (ch == QUOTE) { + if ((pos + 1) < pattern.length() + && pattern.charAt(pos + 1) == QUOTE) { + ++pos; + affix.append("''"); // 'don''t' + } else { + inQuote = false; // 'do' + } + continue; + } + } else { + // Process unquoted characters seen in prefix or suffix + // phase. + switch (ch) { + case ZERO_DIGIT: + phase = 1; + --pos; // Reprocess this character + continue; + case QUOTE: + // A quote outside quotes indicates either the + // opening quote or two quotes, which is a quote + // literal. That is, we have the first quote in 'do' + // or o''clock. + if (ch == QUOTE) { + if ((pos + 1) < pattern.length() + && pattern.charAt(pos + 1) == QUOTE) { + ++pos; + affix.append("''"); // o''clock + } else { + inQuote = true; // 'do' + } + continue; + } + break; + case SEPARATOR: + // Don't allow separators before we see digit + // characters of phase 1, and don't allow separators + // in the second pattern (j == 0). + if (phase == 0 || j == 0) { + throw new IllegalArgumentException( + "Unquoted special character '" + + ch + "' in pattern \"" + pattern + '"'); + } + start = pos + 1; + pos = pattern.length(); + continue; + case MINUS_SIGN: + affix.append("'-"); + continue; + case DECIMAL_SEPARATOR: + case GROUPING_SEPARATOR: + case DIGIT: + case PERCENT: + case PER_MILLE: + case CURRENCY_SIGN: + throw new IllegalArgumentException( + "Unquoted special character '" + ch + + "' in pattern \"" + pattern + '"'); + default: + break; + } + } + // Note that if we are within quotes, or if this is an + // unquoted, non-special character, then we usually fall + // through to here. + affix.append(ch); + break; + + case 1: + // The negative subpattern (j = 0) serves only to specify the + // negative prefix and suffix, so all the phase 1 characters + // e.g. digits, zeroDigit, groupingSeparator, + // decimalSeparator, exponent are ignored + if (j == 0) { + while (pos < pattern.length()) { + char negPatternChar = pattern.charAt(pos); + if (negPatternChar == ZERO_DIGIT) { + ++pos; + } else { + // Not a phase 1 character, consider it as + // suffix and parse it in phase 2 + --pos; //process it again in outer loop + phase = 2; + affix = suffix; + break; + } + } + continue; + } + // consider only '0' as valid pattern char which can appear + // in number part, rest can be either suffix or prefix + if (ch == ZERO_DIGIT) { + zeros = zeros + "0"; + } else { + phase = 2; + affix = suffix; + --pos; + } + break; + } + } + + if (j == 1) { + positivePrefix = prefix.toString(); + positiveSuffix = suffix.toString(); + negativePrefix = positivePrefix; + negativeSuffix = positiveSuffix; + } else { + negativePrefix = prefix.toString(); + negativeSuffix = suffix.toString(); + gotNegative = true; + } + + // If there is no negative pattern, or if the negative pattern is + // identical to the positive pattern, then prepend the minus sign to + // the positive pattern to form the negative pattern. + if (!gotNegative + || (negativePrefix.equals(positivePrefix) + && negativeSuffix.equals(positiveSuffix))) { + negativeSuffix = positiveSuffix; + negativePrefix = "'-" + positivePrefix; + } + } + + // if no 0s are specified in a non empty pattern, it is invalid + if (pattern.length() != 0 && 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.add(positivePrefix); + negativePrefixPatterns.add(negativePrefix); + positiveSuffixPatterns.add(positiveSuffix); + negativeSuffixPatterns.add(negativeSuffix); + divisors.add(computeDivisor(zeros, index)); + } else { + positivePrefixPatterns.add(""); + negativePrefixPatterns.add(""); + positiveSuffixPatterns.add(""); + negativeSuffixPatterns.add(""); + divisors.add(1L); + } + } + + private final transient DigitList digitList = new DigitList(); + private static final int STATUS_INFINITE = 0; + private static final int STATUS_POSITIVE = 1; + private static final int STATUS_LENGTH = 2; + + private static final char ZERO_DIGIT = '0'; + private static final char DIGIT = '#'; + private static final char DECIMAL_SEPARATOR = '.'; + private static final char GROUPING_SEPARATOR = ','; + private static final char MINUS_SIGN = '-'; + private static final char PERCENT = '%'; + private static final char PER_MILLE = '\u2030'; + private static final char SEPARATOR = ';'; + private static final char CURRENCY_SIGN = '\u00A4'; + private static final char QUOTE = '\''; + + // Expanded form of positive/negative prefix/suffix, + // the expanded form contains special characters in + // its localized form, which are used for matching + // while parsing a string to number + private transient List positivePrefixes; + private transient List negativePrefixes; + private transient List positiveSuffixes; + private transient List negativeSuffixes; + + private void expandAffixPatterns() { + positivePrefixes = new ArrayList<>(compactPatterns.length); + negativePrefixes = new ArrayList<>(compactPatterns.length); + positiveSuffixes = new ArrayList<>(compactPatterns.length); + negativeSuffixes = new ArrayList<>(compactPatterns.length); + for (int index = 0; index < compactPatterns.length; index++) { + positivePrefixes.add(expandAffix(positivePrefixPatterns.get(index))); + negativePrefixes.add(expandAffix(negativePrefixPatterns.get(index))); + positiveSuffixes.add(expandAffix(positiveSuffixPatterns.get(index))); + negativeSuffixes.add(expandAffix(negativeSuffixPatterns.get(index))); + } + } + + /** + * Parses a compact number from a string to produce a {@code Number}. + *

+ * The method attempts to parse text starting at the index given by + * {@code pos}. + * If parsing succeeds, then the index of {@code pos} is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * number is returned. The updated {@code pos} can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of {@code pos} is not + * changed, the error index of {@code pos} is set to the index of + * the character where the error occurred, and {@code null} is returned. + *

+ * The value is the numeric part in the given text multiplied + * by the numeric equivalent of the affix attached + * (For example, "K" = 1000 in {@link java.util.Locale#US US locale}). + * The subclass returned depends on the value of + * {@link #isParseBigDecimal}. + *

    + *
  • If {@link #isParseBigDecimal()} is false (the default), + * most integer values are returned as {@code Long} + * objects, no matter how they are written: {@code "17K"} and + * {@code "17.000K"} both parse to {@code Long.valueOf(17000)}. + * If the value cannot fit into {@code Long}, then the result is + * returned as {@code Double}. This includes values with a + * fractional part, infinite values, {@code NaN}, + * and the value -0.0. + *

    + * Callers may use the {@code Number} methods {@code doubleValue}, + * {@code longValue}, etc., to obtain the type they want. + * + *

  • If {@link #isParseBigDecimal()} is true, values are returned + * as {@code BigDecimal} objects. The special cases negative + * and positive infinity and NaN are returned as {@code Double} + * instances holding the values of the corresponding + * {@code Double} constants. + *
+ *

+ * {@code CompactNumberFormat} parses all Unicode characters that represent + * decimal digits, as defined by {@code Character.digit()}. In + * addition, {@code CompactNumberFormat} also recognizes as digits the ten + * consecutive characters starting with the localized zero digit defined in + * the {@code DecimalFormatSymbols} object. + *

+ * {@code CompactNumberFormat} parse does not allow parsing scientific + * notations. For example, parsing a string {@code "1.05E4K"} in + * {@link java.util.Locale#US US locale} breaks at character 'E' + * and returns 1.05. + * + * @param text the string to be parsed + * @param pos a {@code ParsePosition} object with index and error + * index information as described above + * @return the parsed value, or {@code null} if the parse fails + * @exception NullPointerException if {@code text} or + * {@code pos} is null + * + */ + @Override + public Number parse(String text, ParsePosition pos) { + + Objects.requireNonNull(text); + Objects.requireNonNull(pos); + + // lazily expanding the affix patterns, on the first parse + // call on this instance + // if not initialized, expand and load all affixes + if (positivePrefixes == null) { + expandAffixPatterns(); + } + + // The compact number multiplier for parsed string. + // Its value is set on parsing prefix and suffix. For example, + // in the {@link java.util.Locale#US US locale} parsing {@code "1K"} + // sets its value to 1000, as K (thousand) is abbreviated form of 1000. + Number cnfMultiplier = 1L; + + // special case NaN + if (text.regionMatches(pos.index, symbols.getNaN(), + 0, symbols.getNaN().length())) { + pos.index = pos.index + symbols.getNaN().length(); + return Double.NaN; + } + + int position = pos.index; + int oldStart = pos.index; + boolean gotPositive = false; + boolean gotNegative = false; + int matchedPosIndex = -1; + int matchedNegIndex = -1; + String matchedPosPrefix = ""; + String matchedNegPrefix = ""; + // prefix matching + for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { + String positivePrefix = positivePrefixes.get(compactIndex); + String negativePrefix = negativePrefixes.get(compactIndex); + + // first check with the compact prefix which are non empty and + // do not match with default prefix + + // do not break if a match occur; there is a possibility that the + // subsequent affixes may match the longer subsequence in the given + // string. + // For example, matching "Mdx 3" with "M", "Md" as prefix should + // match with "Md" + if (!positivePrefix.isEmpty() && !positivePrefix.equals( + defaultDecimalFormat.getPositivePrefix())) { + if (text.regionMatches(position, positivePrefix, 0, + positivePrefix.length())) { + // look ahead only for the longer match than the previous match + if (matchedPosIndex == -1 + || matchedPosPrefix.length() < positivePrefix.length()) { + matchedPosIndex = compactIndex; + matchedPosPrefix = positivePrefix; + gotPositive = true; + } + } + } + if (!negativePrefix.isEmpty() && !negativePrefix.equals( + defaultDecimalFormat.getNegativePrefix())) { + if (text.regionMatches(position, negativePrefix, 0, + negativePrefix.length())) { + // look ahead only for the longer match than the previous match + if (matchedNegIndex == -1 + || matchedPosPrefix.length() < negativePrefix.length()) { + matchedNegIndex = compactIndex; + matchedNegPrefix = negativePrefix; + gotNegative = true; + } + } + } + } + + // given text does not match the non empty valid compact prefixes + // check with the default prefixes + if (!gotPositive && !gotNegative) { + String positivePrefix = defaultDecimalFormat.getPositivePrefix(); + String negativePrefix = defaultDecimalFormat.getNegativePrefix(); + if (text.regionMatches(pos.index, positivePrefix, 0, + positivePrefix.length())) { + // matches the default positive prefix + matchedPosPrefix = positivePrefix; + gotPositive = true; + } + if (text.regionMatches(pos.index, negativePrefix, 0, + negativePrefix.length())) { + // matches the default negative prefix + matchedNegPrefix = negativePrefix; + gotNegative = true; + } + } + + // if both match, take the longest one + if (gotPositive && gotNegative) { + if (matchedPosPrefix.length() > matchedNegPrefix.length()) { + gotNegative = false; + } else if (matchedPosPrefix.length() < matchedNegPrefix.length()) { + gotPositive = false; + } + } + + // 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]; + + // call DecimalFormat.subparseNumber() method to parse the + // number part of the input text + int numPosition = decimalFormat.subparseNumber(text, position, + digitList, false, false, status); + + Number multiplier; + if (numPosition == -1) { + // unable to parse the number successfully + pos.index = oldStart; + pos.errorIndex = oldStart; + return null; + } + + // if parse integer only is true and the parsing is broken at + // decimal point, then pass/ignore all digits and move pointer + // at the start of suffix, to process the suffix part + if (isParseIntegerOnly() + && text.charAt(numPosition) == symbols.getDecimalSeparator()) { + numPosition++; // pass decimal character + for (; numPosition < text.length(); ++numPosition) { + char ch = text.charAt(numPosition); + int digit = ch - symbols.getZeroDigit(); + if (digit < 0 || digit > 9) { + digit = Character.digit(ch, 10); + // parse all digit characters + if (!(digit >= 0 && digit <= 9)) { + break; + } + } + } + } + + // number parsed successfully; match prefix and + // suffix to obtain multiplier + pos.index = numPosition; + multiplier = matchPrefixAndSuffix(text, pos, + gotPositive ? matchedPosPrefix : matchedNegPrefix, status, + gotPositive, gotNegative); + + if (multiplier.longValue() == -1L) { + return null; + } else if (multiplier.longValue() != 1L) { + cnfMultiplier = multiplier; + } + + // special case INFINITY + if (status[STATUS_INFINITE]) { + if (status[STATUS_POSITIVE]) { + return Double.POSITIVE_INFINITY; + } else { + return Double.NEGATIVE_INFINITY; + } + } + + Number cnfResult; + if (isParseBigDecimal()) { + BigDecimal bigDecimalResult = digitList.getBigDecimal(); + + if (cnfMultiplier.longValue() != 1) { + bigDecimalResult = bigDecimalResult + .multiply(new BigDecimal("" + cnfMultiplier)); + } + if (!status[STATUS_POSITIVE]) { + bigDecimalResult = bigDecimalResult.negate(); + } + return bigDecimalResult; + } else { + if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { + long longResult = digitList.getLong(); + cnfResult = generateParseResult(longResult, false, + longResult < 0, status, cnfMultiplier); + } else { + cnfResult = generateParseResult(digitList.getDouble(), + true, false, status, cnfMultiplier); + } + return cnfResult; + } + } + + /** + * Returns the parsed result by multiplying the parsed number + * with the multiplier representing the prefix and suffix. + * + * @param number parsed number component + * @param gotDouble whether the parsed number contains decimal + * @param gotLongMin whether the parsed number is Long.MIN + * @param status boolean status flags indicating whether the + * value is infinite and whether it is positive + * @param cnfMultiplier compact number multiplier + * @return parsed result + */ + private Number generateParseResult(Number number, boolean gotDouble, + boolean gotLongMin, boolean[] status, + Number cnfMultiplier) { + + if (gotDouble) { + if (cnfMultiplier.longValue() != 1L) { + double doubleResult = number.doubleValue() * cnfMultiplier.doubleValue(); + doubleResult = (double) convertIfNegative(doubleResult, status, gotLongMin); + // check if a double can be represeneted as a long + long longResult = (long) doubleResult; + gotDouble = ((doubleResult != (double) longResult) || + (doubleResult == 0.0 && 1 / doubleResult < 0.0)); + return gotDouble ? (Number) doubleResult : (Number) longResult; + } + } else { + if (cnfMultiplier.longValue() != 1L) { + Number result; + if ((cnfMultiplier instanceof Long) && !gotLongMin) { + long longMultiplier = (long) cnfMultiplier; + try { + result = Math.multiplyExact(number.longValue(), + longMultiplier); + } catch (ArithmeticException ex) { + // if number * longMultiplier can not be represented + // as long return as double + result = number.doubleValue() * cnfMultiplier.doubleValue(); + } + } else { + // cnfMultiplier can not be stored into long or the number + // part is Long.MIN, return as double + result = number.doubleValue() * cnfMultiplier.doubleValue(); + } + return convertIfNegative(result, status, gotLongMin); + } + } + + // default number + return convertIfNegative(number, status, gotLongMin); + } + + /** + * Negate the parsed value if the positive status flag is false + * and the value is not a Long.MIN + * @param number parsed value + * @param status boolean status flags indicating whether the + * value is infinite and whether it is positive + * @param gotLongMin whether the parsed number is Long.MIN + * @return the resulting value + */ + private Number convertIfNegative(Number number, boolean[] status, + boolean gotLongMin) { + if (!status[STATUS_POSITIVE] && !gotLongMin) { + if (number instanceof Long) { + return -(long) number; + } else { + return - (double) number; + } + } else { + return number; + } + } + + /** + * Matches the given {@code matchedPrefix} from the lists of prefixes + * and {@code text} for suffix from the lists of suffixes + * extracted from compact patterns. + * + * @param text the string to parse + * @param parsePosition the {@code ParsePosition} object representing the + * index and error index of the parse string + * @param matchedPrefix prefix extracted which needs to be matched to + * obtain the multiplier + * @param status upon return contains boolean status flags indicating + * whether the value is positive + * @param gotPositive based on the prefix parsed; whether the number is positive + * @param gotNegative based on the prefix parsed; whether the number is negative + * @return the multiplier matching the prefix and suffix; -1 otherwise + */ + private Number matchPrefixAndSuffix(String text, ParsePosition parsePosition, + String matchedPrefix, boolean[] status, boolean gotPositive, + boolean gotNegative) { + + int position = parsePosition.index; + boolean gotPos = false; + boolean gotNeg = false; + int matchedPosIndex = -1; + int matchedNegIndex = -1; + String matchedPosSuffix = ""; + String matchedNegSuffix = ""; + for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { + String positivePrefix = positivePrefixes.get(compactIndex); + String negativePrefix = negativePrefixes.get(compactIndex); + String positiveSuffix = positiveSuffixes.get(compactIndex); + String negativeSuffix = negativeSuffixes.get(compactIndex); + // check the compact pattern suffix only if there is a + // compact prefix match or a default prefix match + // because the compact prefix and suffix should match at the same + // index to obtain the multiplier. + // The prefix match is required because of the possibility of + // same prefix at multiple index, in which case matching the suffix + // is used to obtain the single match + + // do not break if a match occur; there is a possibility that the + // subsequent affixes may match the longer subsequence in the given + // string. + // For example, matching "3Mdx" with "M", "Md" should match with "Md" + + if (positivePrefix.equals(matchedPrefix) + || matchedPrefix.equals(defaultDecimalFormat.getPositivePrefix())) { + if (!positiveSuffix.isEmpty() + && !positiveSuffix.equals(defaultDecimalFormat.getPositiveSuffix()) + && text.regionMatches(position, positiveSuffix, 0, positiveSuffix.length())) { + // look ahead only for the longer match than the previous match + if (matchedPosIndex == -1 || matchedPosSuffix.length() < positiveSuffix.length()) { + matchedPosIndex = compactIndex; + matchedPosSuffix = positiveSuffix; + gotPos = true; + } + } + } + + if (negativePrefix.equals(matchedPrefix) + || matchedPrefix.equals(defaultDecimalFormat.getNegativePrefix())) { + if (!negativeSuffix.isEmpty() + && !negativeSuffix.equals(defaultDecimalFormat.getNegativeSuffix()) + && text.regionMatches(position, negativeSuffix, 0, negativeSuffix.length())) { + // look ahead only for the longer match than the previous match + if (matchedNegIndex == -1 || matchedNegSuffix.length() < negativeSuffix.length()) { + matchedNegIndex = compactIndex; + matchedNegSuffix = negativeSuffix; + gotNeg = true; + } + } + } + } + + // suffix in the given text does not match with the compact + // patterns suffixes; match with the default suffix + if (!gotPos && !gotNeg) { + String positiveSuffix = defaultDecimalFormat.getPositiveSuffix(); + String negativeSuffix = defaultDecimalFormat.getNegativeSuffix(); + if (text.regionMatches(position, positiveSuffix, 0, + positiveSuffix.length())) { + //matches the default positive prefix + matchedPosSuffix = positiveSuffix; + gotPos = true; + } + if (text.regionMatches(position, negativeSuffix, 0, + negativeSuffix.length())) { + // matches the default negative suffix + matchedNegSuffix = negativeSuffix; + gotNeg = true; + } + } + + // if both matches, take the longest one + if (gotPos && gotNeg) { + if (matchedPosSuffix.length() > matchedNegSuffix.length()) { + gotNeg = false; + } else if (matchedPosSuffix.length() < matchedNegSuffix.length()) { + gotPos = false; + } else { + // if longest comparison fails; take the positive and negative + // sign of matching prefix + gotPos = gotPositive; + gotNeg = gotNegative; + } + } + + // fail if neither or both + if (gotPos == gotNeg) { + parsePosition.errorIndex = position; + return -1L; + } + + Number cnfMultiplier = 1L; + // update the parse position index and take compact multiplier + // only if it matches the compact suffix, not the default + // suffix; else multiplier should be 1 + if (gotPos) { + parsePosition.index = position + matchedPosSuffix.length(); + cnfMultiplier = matchedPosIndex != -1 + ? divisors.get(matchedPosIndex) : 1L; + } else if (gotNeg) { + parsePosition.index = position + matchedNegSuffix.length(); + cnfMultiplier = matchedNegIndex != -1 + ? divisors.get(matchedNegIndex) : 1L; + } + status[STATUS_POSITIVE] = gotPos; + return cnfMultiplier; + } + + /** + * Reconstitutes this {@code CompactNumberFormat} from a stream + * (that is, deserializes it) after performing some validations. + * This method throws InvalidObjectException, if the stream data is invalid + * because of the following reasons, + *

    + *
  • If any of the {@code decimalPattern}, {@code compactPatterns}, + * {@code symbols} or {@code roundingMode} is {@code null}. + *
  • If the {@code decimalPattern} or the {@code compactPatterns} array + * contains an invalid pattern or if a {@code null} appears in the array of + * compact patterns. + *
  • If the {@code minimumIntegerDigits} is greater than the + * {@code maximumIntegerDigits} or the {@code minimumFractionDigits} is + * greater than the {@code maximumFractionDigits}. This check is performed + * by superclass's Object. + *
  • If any of the minimum/maximum integer/fraction digit count is + * negative. This check is performed by superclass's readObject. + *
  • If the minimum or maximum integer digit count is larger than 309 or + * if the minimum or maximum fraction digit count is larger than 340. + *
  • If the grouping size is negative or larger than 127. + *
+ * + * @param inStream the stream + * @throws IOException if an I/O error occurs + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + */ + private void readObject(ObjectInputStream inStream) throws IOException, + ClassNotFoundException { + + inStream.defaultReadObject(); + if (decimalPattern == null || compactPatterns == null + || symbols == null || roundingMode == null) { + throw new InvalidObjectException("One of the 'decimalPattern'," + + " 'compactPatterns', 'symbols' or 'roundingMode'" + + " is null"); + } + + // Check only the maximum counts because NumberFormat.readObject has + // already ensured that the maximum is greater than the minimum count. + if (getMaximumIntegerDigits() > DecimalFormat.DOUBLE_INTEGER_DIGITS + || getMaximumFractionDigits() > DecimalFormat.DOUBLE_FRACTION_DIGITS) { + throw new InvalidObjectException("Digit count out of range"); + } + + // Check if the grouping size is negative, on an attempt to + // put value > 127, it wraps around, so check just negative value + if (groupingSize < 0) { + throw new InvalidObjectException("Grouping size is negative"); + } + + try { + processCompactPatterns(); + } catch (IllegalArgumentException ex) { + throw new InvalidObjectException(ex.getMessage()); + } + + decimalFormat = new DecimalFormat(SPECIAL_PATTERN, symbols); + decimalFormat.setMaximumFractionDigits(getMaximumFractionDigits()); + decimalFormat.setMinimumFractionDigits(getMinimumFractionDigits()); + decimalFormat.setMaximumIntegerDigits(getMaximumIntegerDigits()); + decimalFormat.setMinimumIntegerDigits(getMinimumIntegerDigits()); + decimalFormat.setRoundingMode(getRoundingMode()); + decimalFormat.setGroupingSize(getGroupingSize()); + decimalFormat.setGroupingUsed(isGroupingUsed()); + decimalFormat.setParseIntegerOnly(isParseIntegerOnly()); + + try { + defaultDecimalFormat = new DecimalFormat(decimalPattern, symbols); + defaultDecimalFormat.setMaximumFractionDigits(0); + } catch (IllegalArgumentException ex) { + throw new InvalidObjectException(ex.getMessage()); + } + + } + + /** + * Sets the maximum number of digits allowed in the integer portion of a + * number. + * The maximum allowed integer range is 309, if the {@code newValue} > 309, + * then the maximum integer digits count is set to 309. Negative input + * values are replaced with 0. + * + * @param newValue the maximum number of integer digits to be shown + * @see #getMaximumIntegerDigits() + */ + @Override + public void setMaximumIntegerDigits(int newValue) { + // The maximum integer digits is checked with the allowed range before calling + // the DecimalFormat.setMaximumIntegerDigits, which performs the negative check + // on the given newValue while setting it as max integer digits + // e.g. if a negative value is specified, it is replaced with 0 + decimalFormat.setMaximumIntegerDigits(Math.min(newValue, + DecimalFormat.DOUBLE_INTEGER_DIGITS)); + super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); + if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) { + decimalFormat.setMinimumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); + super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); + } + } + + /** + * Sets the minimum number of digits allowed in the integer portion of a + * number. + * The maximum allowed integer range is 309, if the {@code newValue} > 309, + * then the minimum integer digits count is set to 309. Negative input + * values are replaced with 0. + * + * @param newValue the minimum number of integer digits to be shown + * @see #getMinimumIntegerDigits() + */ + @Override + public void setMinimumIntegerDigits(int newValue) { + // The minimum integer digits is checked with the allowed range before calling + // the DecimalFormat.setMinimumIntegerDigits, which performs check on the given + // newValue while setting it as min integer digits e.g. if a negative + // value is specified, it is replaced with 0 + decimalFormat.setMinimumIntegerDigits(Math.min(newValue, + DecimalFormat.DOUBLE_INTEGER_DIGITS)); + super.setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); + if (decimalFormat.getMinimumIntegerDigits() > decimalFormat.getMaximumIntegerDigits()) { + decimalFormat.setMaximumIntegerDigits(decimalFormat.getMinimumIntegerDigits()); + super.setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits()); + } + } + + /** + * Sets the minimum number of digits allowed in the fraction portion of a + * number. + * The maximum allowed fraction range is 340, if the {@code newValue} > 340, + * then the minimum fraction digits count is set to 340. Negative input + * values are replaced with 0. + * + * @param newValue the minimum number of fraction digits to be shown + * @see #getMinimumFractionDigits() + */ + @Override + public void setMinimumFractionDigits(int newValue) { + // The minimum fraction digits is checked with the allowed range before + // calling the DecimalFormat.setMinimumFractionDigits, which performs + // check on the given newValue while setting it as min fraction + // digits e.g. if a negative value is specified, it is replaced with 0 + decimalFormat.setMinimumFractionDigits(Math.min(newValue, + DecimalFormat.DOUBLE_FRACTION_DIGITS)); + super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); + if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) { + decimalFormat.setMaximumFractionDigits(decimalFormat.getMinimumFractionDigits()); + super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); + } + } + + /** + * Sets the maximum number of digits allowed in the fraction portion of a + * number. + * The maximum allowed fraction range is 340, if the {@code newValue} > 340, + * then the maximum fraction digits count is set to 340. Negative input + * values are replaced with 0. + * + * @param newValue the maximum number of fraction digits to be shown + * @see #getMaximumFractionDigits() + */ + @Override + public void setMaximumFractionDigits(int newValue) { + // The maximum fraction digits is checked with the allowed range before + // calling the DecimalFormat.setMaximumFractionDigits, which performs + // check on the given newValue while setting it as max fraction digits + // e.g. if a negative value is specified, it is replaced with 0 + decimalFormat.setMaximumFractionDigits(Math.min(newValue, + DecimalFormat.DOUBLE_FRACTION_DIGITS)); + super.setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits()); + if (decimalFormat.getMinimumFractionDigits() > decimalFormat.getMaximumFractionDigits()) { + decimalFormat.setMinimumFractionDigits(decimalFormat.getMaximumFractionDigits()); + super.setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits()); + } + } + + /** + * Gets the {@link java.math.RoundingMode} used in this + * {@code CompactNumberFormat}. + * + * @return the {@code RoundingMode} used for this + * {@code CompactNumberFormat} + * @see #setRoundingMode(RoundingMode) + */ + @Override + public RoundingMode getRoundingMode() { + return roundingMode; + } + + /** + * Sets the {@link java.math.RoundingMode} used in this + * {@code CompactNumberFormat}. + * + * @param roundingMode the {@code RoundingMode} to be used + * @see #getRoundingMode() + * @throws NullPointerException if {@code roundingMode} is {@code null} + */ + @Override + public void setRoundingMode(RoundingMode roundingMode) { + decimalFormat.setRoundingMode(roundingMode); + this.roundingMode = roundingMode; + } + + /** + * Returns the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the compact number {@code "12,347 trillion"} for the + * {@link java.util.Locale#US US locale}, the grouping size is 3. + * + * @return the grouping size + * @see #setGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + * @see java.text.DecimalFormatSymbols#getGroupingSeparator + */ + public int getGroupingSize() { + return groupingSize; + } + + /** + * Sets the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the compact number {@code "12,347 trillion"} for the + * {@link java.util.Locale#US US locale}, the grouping size is 3. The grouping + * size must be greater than or equal to zero and less than or equal to 127. + * + * @param newValue the new grouping size + * @see #getGroupingSize + * @see java.text.NumberFormat#setGroupingUsed + * @see java.text.DecimalFormatSymbols#setGroupingSeparator + * @throws IllegalArgumentException if {@code newValue} is negative or + * larger than 127 + */ + public void setGroupingSize(int newValue) { + if (newValue < 0 || newValue > 127) { + throw new IllegalArgumentException( + "The value passed is negative or larger than 127"); + } + groupingSize = (byte) newValue; + decimalFormat.setGroupingSize(groupingSize); + } + + /** + * Returns true if grouping is used in this format. For example, with + * grouping on and grouping size set to 3, the number {@code 12346567890987654} + * can be formatted as {@code "12,347 trillion"} in the + * {@link java.util.Locale#US US locale}. + * The grouping separator is locale dependent. + * + * @return {@code true} if grouping is used; + * {@code false} otherwise + * @see #setGroupingUsed + */ + @Override + public boolean isGroupingUsed() { + return super.isGroupingUsed(); + } + + /** + * Sets whether or not grouping will be used in this format. + * + * @param newValue {@code true} if grouping is used; + * {@code false} otherwise + * @see #isGroupingUsed + */ + @Override + public void setGroupingUsed(boolean newValue) { + decimalFormat.setGroupingUsed(newValue); + super.setGroupingUsed(newValue); + } + + /** + * Returns true if this format parses only an integer from the number + * component of a compact number. + * Parsing an integer means that only an integer is considered from the + * number component, prefix/suffix is still considered to compute the + * resulting output. + * For example in the {@link java.util.Locale#US US locale}, if this method + * returns {@code true}, the string {@code "1234.78 thousand"} would be + * parsed as the value {@code 1234000} (1234 (integer part) * 1000 + * (thousand)) and the fractional part would be skipped. + * The exact format accepted by the parse operation is locale dependent. + * + * @return {@code true} if compact numbers should be parsed as integers + * only; {@code false} otherwise + */ + @Override + public boolean isParseIntegerOnly() { + return super.isParseIntegerOnly(); + } + + /** + * Sets whether or not this format parses only an integer from the number + * component of a compact number. + * + * @param value {@code true} if compact numbers should be parsed as + * integers only; {@code false} otherwise + * @see #isParseIntegerOnly + */ + @Override + public void setParseIntegerOnly(boolean value) { + decimalFormat.setParseIntegerOnly(value); + super.setParseIntegerOnly(value); + } + + /** + * Returns whether the {@link #parse(String, ParsePosition)} + * method returns {@code BigDecimal}. The default value is false. + * + * @return {@code true} if the parse method returns BigDecimal; + * {@code false} otherwise + * @see #setParseBigDecimal + * + */ + public boolean isParseBigDecimal() { + return parseBigDecimal; + } + + /** + * Sets whether the {@link #parse(String, ParsePosition)} + * method returns {@code BigDecimal}. + * + * @param newValue {@code true} if the parse method returns BigDecimal; + * {@code false} otherwise + * @see #isParseBigDecimal + * + */ + public void setParseBigDecimal(boolean newValue) { + parseBigDecimal = newValue; + } + + /** + * Checks if this {@code CompactNumberFormat} is equal to the + * specified {@code obj}. The objects of type {@code CompactNumberFormat} + * are compared, other types return false; obeys the general contract of + * {@link java.lang.Object#equals(java.lang.Object) Object.equals}. + * + * @param obj the object to compare with + * @return true if this is equal to the other {@code CompactNumberFormat} + */ + @Override + public boolean equals(Object obj) { + + if (!super.equals(obj)) { + return false; + } + + CompactNumberFormat other = (CompactNumberFormat) obj; + return decimalPattern.equals(other.decimalPattern) + && symbols.equals(other.symbols) + && Arrays.equals(compactPatterns, other.compactPatterns) + && roundingMode.equals(other.roundingMode) + && groupingSize == other.groupingSize + && parseBigDecimal == other.parseBigDecimal; + } + + /** + * Returns the hash code for this {@code CompactNumberFormat} instance. + * + * @return hash code for this {@code CompactNumberFormat} + */ + @Override + public int hashCode() { + return 31 * super.hashCode() + + Objects.hash(decimalPattern, symbols, roundingMode) + + Arrays.hashCode(compactPatterns) + groupingSize + + Boolean.hashCode(parseBigDecimal); + } + + /** + * Creates and returns a copy of this {@code CompactNumberFormat} + * instance. + * + * @return a clone of this instance + */ + @Override + public CompactNumberFormat clone() { + CompactNumberFormat other = (CompactNumberFormat) super.clone(); + other.compactPatterns = compactPatterns.clone(); + other.symbols = (DecimalFormatSymbols) symbols.clone(); + return other; + } + +} + --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/CompactFormatAndParseHelper.java 2018-11-28 14:09:11.962276887 +0530 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import static org.testng.Assert.*; + +class CompactFormatAndParseHelper { + + static void testFormat(NumberFormat cnf, Object number, + String expected) { + String result = cnf.format(number); + assertEquals(result, expected, "Incorrect formatting of the number '" + + number + "'"); + } + + static void testParse(NumberFormat cnf, String parseString, + Number expected, ParsePosition position, Class returnType) throws ParseException { + + Number number; + if (position == null) { + number = cnf.parse(parseString); + } else { + number = cnf.parse(parseString, position); + } + + if (returnType != null) { + assertEquals(number.getClass(), returnType, "Incorrect return type for string" + parseString); + } + + if (number instanceof Double) { + assertEquals(number.doubleValue(), (double) expected, + "Incorrect parsing of the string '" + parseString + "'"); + } else if (number instanceof Long) { + assertEquals(number.longValue(), (long) expected, + "Incorrect parsing of the string '" + parseString + "'"); + } else if (number instanceof BigDecimal) { + BigDecimal num = (BigDecimal) number; + assertEquals(num, (BigDecimal) expected, + "Incorrect parsing of the string '" + parseString + "'"); + } else { + assertEquals(number, expected, "Incorrect parsing of the string '" + + parseString + "'"); + } + } +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestCNFRounding.java 2018-11-28 14:09:12.798276887 +0530 @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the rounding of formatted number in compact number formatting + * @run testng/othervm TestCNFRounding + */ + +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; +import static org.testng.Assert.*; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestCNFRounding { + + private static final List MODES = List.of( + RoundingMode.HALF_EVEN, + RoundingMode.HALF_UP, + RoundingMode.HALF_DOWN, + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR); + + @DataProvider(name = "roundingData") + Object[][] roundingData() { + return new Object[][]{ + // Number, half_even, half_up, half_down, up, down, ceiling, floor + {5500, new String[]{"6K", "6K", "5K", "6K", "5K", "6K", "5K"}}, + {2500, new String[]{"2K", "3K", "2K", "3K", "2K", "3K", "2K"}}, + {1600, new String[]{"2K", "2K", "2K", "2K", "1K", "2K", "1K"}}, + {1100, new String[]{"1K", "1K", "1K", "2K", "1K", "2K", "1K"}}, + {1000, new String[]{"1K", "1K", "1K", "1K", "1K", "1K", "1K"}}, + {-1000, new String[]{"-1K", "-1K", "-1K", "-1K", "-1K", "-1K", "-1K"}}, + {-1100, new String[]{"-1K", "-1K", "-1K", "-2K", "-1K", "-1K", "-2K"}}, + {-1600, new String[]{"-2K", "-2K", "-2K", "-2K", "-1K", "-1K", "-2K"}}, + {-2500, new String[]{"-2K", "-3K", "-2K", "-3K", "-2K", "-2K", "-3K"}}, + {-5500, new String[]{"-6K", "-6K", "-5K", "-6K", "-5K", "-5K", "-6K"}}, + {5501, new String[]{"6K", "6K", "6K", "6K", "5K", "6K", "5K"}}, + {-5501, new String[]{"-6K", "-6K", "-6K", "-6K", "-5K", "-5K", "-6K"}}, + {1001, new String[]{"1K", "1K", "1K", "2K", "1K", "2K", "1K"}}, + {-1001, new String[]{"-1K", "-1K", "-1K", "-2K", "-1K", "-1K", "-2K"}}, + {4501, new String[]{"5K", "5K", "5K", "5K", "4K", "5K", "4K"}}, + {-4501, new String[]{"-5K", "-5K", "-5K", "-5K", "-4K", "-4K", "-5K"}}, + {4500, new String[]{"4K", "5K", "4K", "5K", "4K", "5K", "4K"}}, + {-4500, new String[]{"-4K", "-5K", "-4K", "-5K", "-4K", "-4K", "-5K"}},}; + } + + @DataProvider(name = "roundingFract") + Object[][] roundingFract() { + return new Object[][]{ + // Number, half_even, half_up, half_down, up, down, ceiling, floor + {5550, new String[]{"5.5K", "5.5K", "5.5K", "5.6K", "5.5K", "5.6K", "5.5K"}}, + {2550, new String[]{"2.5K", "2.5K", "2.5K", "2.6K", "2.5K", "2.6K", "2.5K"}}, + {1660, new String[]{"1.7K", "1.7K", "1.7K", "1.7K", "1.6K", "1.7K", "1.6K"}}, + {1110, new String[]{"1.1K", "1.1K", "1.1K", "1.2K", "1.1K", "1.2K", "1.1K"}}, + {1000, new String[]{"1.0K", "1.0K", "1.0K", "1.0K", "1.0K", "1.0K", "1.0K"}}, + {-1000, new String[]{"-1.0K", "-1.0K", "-1.0K", "-1.0K", "-1.0K", "-1.0K", "-1.0K"}}, + {-1110, new String[]{"-1.1K", "-1.1K", "-1.1K", "-1.2K", "-1.1K", "-1.1K", "-1.2K"}}, + {-1660, new String[]{"-1.7K", "-1.7K", "-1.7K", "-1.7K", "-1.6K", "-1.6K", "-1.7K"}}, + {-2550, new String[]{"-2.5K", "-2.5K", "-2.5K", "-2.6K", "-2.5K", "-2.5K", "-2.6K"}}, + {-5550, new String[]{"-5.5K", "-5.5K", "-5.5K", "-5.6K", "-5.5K", "-5.5K", "-5.6K"}}, + {5551, new String[]{"5.6K", "5.6K", "5.6K", "5.6K", "5.5K", "5.6K", "5.5K"}}, + {-5551, new String[]{"-5.6K", "-5.6K", "-5.6K", "-5.6K", "-5.5K", "-5.5K", "-5.6K"}}, + {1001, new String[]{"1.0K", "1.0K", "1.0K", "1.1K", "1.0K", "1.1K", "1.0K"}}, + {-1001, new String[]{"-1.0K", "-1.0K", "-1.0K", "-1.1K", "-1.0K", "-1.0K", "-1.1K"}}, + {4551, new String[]{"4.6K", "4.6K", "4.6K", "4.6K", "4.5K", "4.6K", "4.5K"}}, + {-4551, new String[]{"-4.6K", "-4.6K", "-4.6K", "-4.6K", "-4.5K", "-4.5K", "-4.6K"}}, + {4500, new String[]{"4.5K", "4.5K", "4.5K", "4.5K", "4.5K", "4.5K", "4.5K"}}, + {-4500, new String[]{"-4.5K", "-4.5K", "-4.5K", "-4.5K", "-4.5K", "-4.5K", "-4.5K"}},}; + } + + @DataProvider(name = "rounding2Fract") + Object[][] rounding2Fract() { + return new Object[][]{ + // Number, half_even, half_up, half_down + {1115, new String[]{"1.11K", "1.11K", "1.11K"}}, + {1125, new String[]{"1.12K", "1.13K", "1.12K"}}, + {1135, new String[]{"1.14K", "1.14K", "1.14K"}}, + {3115, new String[]{"3.12K", "3.12K", "3.12K"}}, + {3125, new String[]{"3.12K", "3.13K", "3.12K"}}, + {3135, new String[]{"3.13K", "3.13K", "3.13K"}}, + {6865, new String[]{"6.87K", "6.87K", "6.87K"}}, + {6875, new String[]{"6.88K", "6.88K", "6.87K"}}, + {6885, new String[]{"6.88K", "6.88K", "6.88K"}}, + {3124, new String[]{"3.12K", "3.12K", "3.12K"}}, + {3126, new String[]{"3.13K", "3.13K", "3.13K"}}, + {3128, new String[]{"3.13K", "3.13K", "3.13K"}}, + {6864, new String[]{"6.86K", "6.86K", "6.86K"}}, + {6865, new String[]{"6.87K", "6.87K", "6.87K"}}, + {6868, new String[]{"6.87K", "6.87K", "6.87K"}}, + {4685, new String[]{"4.68K", "4.68K", "4.68K"}}, + {4687, new String[]{"4.69K", "4.69K", "4.69K"}}, + {4686, new String[]{"4.69K", "4.69K", "4.69K"}},}; + } + + @Test(expectedExceptions = NullPointerException.class) + public void testNullMode() { + NumberFormat fmt = NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + fmt.setRoundingMode(null); + } + + @Test + public void testDefaultRoundingMode() { + NumberFormat fmt = NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + assertEquals(fmt.getRoundingMode(), RoundingMode.HALF_EVEN, + "Default RoundingMode should be " + RoundingMode.HALF_EVEN); + } + + @Test(dataProvider = "roundingData") + public void testRounding(Object number, String[] expected) { + for (int index = 0; index < MODES.size(); index++) { + testRoundingMode(number, expected[index], 0, MODES.get(index)); + } + } + + @Test(dataProvider = "roundingFract") + public void testRoundingFract(Object number, String[] expected) { + for (int index = 0; index < MODES.size(); index++) { + testRoundingMode(number, expected[index], 1, MODES.get(index)); + } + } + + @Test(dataProvider = "rounding2Fract") + public void testRounding2Fract(Object number, String[] expected) { + List rModes = List.of(RoundingMode.HALF_EVEN, + RoundingMode.HALF_UP, RoundingMode.HALF_DOWN); + for (int index = 0; index < rModes.size(); index++) { + testRoundingMode(number, expected[index], 2, rModes.get(index)); + } + } + + private void testRoundingMode(Object number, String expected, + int fraction, RoundingMode rounding) { + NumberFormat fmt = NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + fmt.setRoundingMode(rounding); + assertEquals(fmt.getRoundingMode(), rounding, + "RoundingMode set is not returned by getRoundingMode"); + + fmt.setMinimumFractionDigits(fraction); + String result = fmt.format(number); + assertEquals(result, expected, "Incorrect formatting of number " + + number + " using rounding mode: " + rounding); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestCompactNumber.java 2018-11-28 14:09:13.626276887 +0530 @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the functioning of compact number format + * @modules jdk.localedata + * @run testng/othervm TestCompactNumber + */ +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.Format; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; +import java.util.stream.Stream; +import static org.testng.Assert.*; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestCompactNumber { + + private static final NumberFormat FORMAT_DZ_LONG = NumberFormat + .getCompactNumberInstance(new Locale("dz"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_EN_US_SHORT = NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + + private static final NumberFormat FORMAT_EN_LONG = NumberFormat + .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_HI_IN_LONG = NumberFormat + .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_JA_JP_SHORT = NumberFormat + .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT); + + private static final NumberFormat FORMAT_IT_SHORT = NumberFormat + .getCompactNumberInstance(new Locale("it"), NumberFormat.Style.SHORT); + + private static final NumberFormat FORMAT_CA_LONG = NumberFormat + .getCompactNumberInstance(new Locale("ca"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_AS_LONG = NumberFormat + .getCompactNumberInstance(new Locale("as"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_BRX_SHORT = NumberFormat + .getCompactNumberInstance(new Locale("brx"), NumberFormat.Style.SHORT); + + private static final NumberFormat FORMAT_SW_LONG = NumberFormat + .getCompactNumberInstance(new Locale("sw"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_SE_SHORT = NumberFormat + .getCompactNumberInstance(new Locale("se"), NumberFormat.Style.SHORT); + + @DataProvider(name = "format") + Object[][] compactFormatData() { + return new Object[][]{ + // compact number format instance, number to format, formatted output + {FORMAT_DZ_LONG, 1000.09, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55" + + "\u0FB2\u0F42 \u0F21"}, + {FORMAT_DZ_LONG, -999.99, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55" + + "\u0FB2\u0F42 \u0F21"}, + {FORMAT_DZ_LONG, -0.0, "-\u0F20"}, + {FORMAT_DZ_LONG, 3000L, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55" + + "\u0FB2\u0F42 \u0F23"}, + {FORMAT_DZ_LONG, new BigInteger("12345678901234567890"), "\u0F51" + + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66" + + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"}, + // negative + {FORMAT_DZ_LONG, new BigInteger("-12345678901234567890"), "-\u0F51" + + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66" + + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"}, + {FORMAT_DZ_LONG, new BigDecimal("12345678901234567890.89"), "\u0F51" + + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66" + + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"}, + {FORMAT_DZ_LONG, new BigDecimal("-12345678901234567890.89"), "-\u0F51" + + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66" + + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"}, + // Zeros + {FORMAT_EN_US_SHORT, 0, "0"}, + {FORMAT_EN_US_SHORT, 0.0, "0"}, + {FORMAT_EN_US_SHORT, -0.0, "-0"}, + // Less than 1000 no suffix + {FORMAT_EN_US_SHORT, 499, "499"}, + // Boundary number + {FORMAT_EN_US_SHORT, 1000.0, "1K"}, + // Long + {FORMAT_EN_US_SHORT, 3000L, "3K"}, + {FORMAT_EN_US_SHORT, 30000L, "30K"}, + {FORMAT_EN_US_SHORT, 300000L, "300K"}, + {FORMAT_EN_US_SHORT, 3000000L, "3M"}, + {FORMAT_EN_US_SHORT, 30000000L, "30M"}, + {FORMAT_EN_US_SHORT, 300000000L, "300M"}, + {FORMAT_EN_US_SHORT, 3000000000L, "3B"}, + {FORMAT_EN_US_SHORT, 30000000000L, "30B"}, + {FORMAT_EN_US_SHORT, 300000000000L, "300B"}, + {FORMAT_EN_US_SHORT, 3000000000000L, "3T"}, + {FORMAT_EN_US_SHORT, 30000000000000L, "30T"}, + {FORMAT_EN_US_SHORT, 300000000000000L, "300T"}, + {FORMAT_EN_US_SHORT, 3000000000000000L, "3000T"}, + // Negatives + {FORMAT_EN_US_SHORT, -3000L, "-3K"}, + {FORMAT_EN_US_SHORT, -30000L, "-30K"}, + {FORMAT_EN_US_SHORT, -300000L, "-300K"}, + {FORMAT_EN_US_SHORT, -3000000L, "-3M"}, + {FORMAT_EN_US_SHORT, -30000000L, "-30M"}, + {FORMAT_EN_US_SHORT, -300000000L, "-300M"}, + {FORMAT_EN_US_SHORT, -3000000000L, "-3B"}, + {FORMAT_EN_US_SHORT, -30000000000L, "-30B"}, + {FORMAT_EN_US_SHORT, -300000000000L, "-300B"}, + {FORMAT_EN_US_SHORT, -3000000000000L, "-3T"}, + {FORMAT_EN_US_SHORT, -30000000000000L, "-30T"}, + {FORMAT_EN_US_SHORT, -300000000000000L, "-300T"}, + {FORMAT_EN_US_SHORT, -3000000000000000L, "-3000T"}, + // Double + {FORMAT_EN_US_SHORT, 3000.0, "3K"}, + {FORMAT_EN_US_SHORT, 30000.0, "30K"}, + {FORMAT_EN_US_SHORT, 300000.0, "300K"}, + {FORMAT_EN_US_SHORT, 3000000.0, "3M"}, + {FORMAT_EN_US_SHORT, 30000000.0, "30M"}, + {FORMAT_EN_US_SHORT, 300000000.0, "300M"}, + {FORMAT_EN_US_SHORT, 3000000000.0, "3B"}, + {FORMAT_EN_US_SHORT, 30000000000.0, "30B"}, + {FORMAT_EN_US_SHORT, 300000000000.0, "300B"}, + {FORMAT_EN_US_SHORT, 3000000000000.0, "3T"}, + {FORMAT_EN_US_SHORT, 30000000000000.0, "30T"}, + {FORMAT_EN_US_SHORT, 300000000000000.0, "300T"}, + {FORMAT_EN_US_SHORT, 3000000000000000.0, "3000T"}, + // Negatives + {FORMAT_EN_US_SHORT, -3000.0, "-3K"}, + {FORMAT_EN_US_SHORT, -30000.0, "-30K"}, + {FORMAT_EN_US_SHORT, -300000.0, "-300K"}, + {FORMAT_EN_US_SHORT, -3000000.0, "-3M"}, + {FORMAT_EN_US_SHORT, -30000000.0, "-30M"}, + {FORMAT_EN_US_SHORT, -300000000.0, "-300M"}, + {FORMAT_EN_US_SHORT, -3000000000.0, "-3B"}, + {FORMAT_EN_US_SHORT, -30000000000.0, "-30B"}, + {FORMAT_EN_US_SHORT, -300000000000.0, "-300B"}, + {FORMAT_EN_US_SHORT, -3000000000000.0, "-3T"}, + {FORMAT_EN_US_SHORT, -30000000000000.0, "-30T"}, + {FORMAT_EN_US_SHORT, -300000000000000.0, "-300T"}, + {FORMAT_EN_US_SHORT, -3000000000000000.0, "-3000T"}, + // BigInteger + {FORMAT_EN_US_SHORT, new BigInteger("12345678901234567890"), + "12345679T"}, + {FORMAT_EN_US_SHORT, new BigInteger("-12345678901234567890"), + "-12345679T"}, + //BigDecimal + {FORMAT_EN_US_SHORT, new BigDecimal("12345678901234567890.89"), + "12345679T"}, + {FORMAT_EN_US_SHORT, new BigDecimal("-12345678901234567890.89"), + "-12345679T"}, + {FORMAT_EN_US_SHORT, new BigDecimal("12345678901234567890123466767.89"), + "12345678901234568T"}, + {FORMAT_EN_US_SHORT, new BigDecimal( + "12345678901234567890878732267863209.89"), + "12345678901234567890879T"}, + // number as exponent + {FORMAT_EN_US_SHORT, 9.78313E+3, "10K"}, + // Less than 1000 no suffix + {FORMAT_EN_LONG, 999, "999"}, + // Round the value and then format + {FORMAT_EN_LONG, 999.99, "1 thousand"}, + // 10 thousand + {FORMAT_EN_LONG, 99000, "99 thousand"}, + // Long path + {FORMAT_EN_LONG, 330000, "330 thousand"}, + // Double path + {FORMAT_EN_LONG, 3000.90, "3 thousand"}, + // BigInteger path + {FORMAT_EN_LONG, new BigInteger("12345678901234567890"), + "12345679 trillion"}, + //BigDecimal path + {FORMAT_EN_LONG, new BigDecimal("12345678901234567890.89"), + "12345679 trillion"}, + // Less than 1000 no suffix + {FORMAT_HI_IN_LONG, -999, "-999"}, + // Round the value with 0 fraction digits and format it + {FORMAT_HI_IN_LONG, -999.99, "-1 \u0939\u091C\u093C\u093E\u0930"}, + // 10 thousand + {FORMAT_HI_IN_LONG, 99000, "99 \u0939\u091C\u093C\u093E\u0930"}, + // Long path + {FORMAT_HI_IN_LONG, 330000, "3 \u0932\u093E\u0916"}, + // Double path + {FORMAT_HI_IN_LONG, 3000.90, "3 \u0939\u091C\u093C\u093E\u0930"}, + // BigInteger path + {FORMAT_HI_IN_LONG, new BigInteger("12345678901234567890"), + "123456789 \u0916\u0930\u092C"}, + // BigDecimal path + {FORMAT_HI_IN_LONG, new BigDecimal("12345678901234567890.89"), + "123456789 \u0916\u0930\u092C"}, + // 1000 does not have any suffix in "ja" locale + {FORMAT_JA_JP_SHORT, -999.99, "-1,000"}, + // 0-9999 does not have any suffix + {FORMAT_JA_JP_SHORT, 9999, "9,999"}, + // 99000/10000 => 9.9\u4E07 rounded to 10\u4E07 + {FORMAT_JA_JP_SHORT, 99000, "10\u4E07"}, + // Negative + {FORMAT_JA_JP_SHORT, -99000, "-10\u4E07"}, + // Long path + {FORMAT_JA_JP_SHORT, 330000, "33\u4E07"}, + // Double path + {FORMAT_JA_JP_SHORT, 3000.90, "3,001"}, + // BigInteger path + {FORMAT_JA_JP_SHORT, new BigInteger("12345678901234567890"), + "12345679\u5146"}, + // BigDecimal path + {FORMAT_JA_JP_SHORT, new BigDecimal("12345678901234567890.89"), + "12345679\u5146"}, + // less than 1000 no suffix + {FORMAT_IT_SHORT, 499, "499"}, + // Boundary number + {FORMAT_IT_SHORT, 1000, "1.000"}, + // Long path + {FORMAT_IT_SHORT, 3000000L, "3\u00a0Mln"}, + // Double path + {FORMAT_IT_SHORT, 3000000.0, "3\u00a0Mln"}, + // BigInteger path + {FORMAT_IT_SHORT, new BigInteger("12345678901234567890"), + "12345679\u00a0Bln"}, + // BigDecimal path + {FORMAT_IT_SHORT, new BigDecimal("12345678901234567890.89"), + "12345679\u00a0Bln"}, + {FORMAT_CA_LONG, 999, "999"}, + {FORMAT_CA_LONG, 999.99, "1 miler"}, + {FORMAT_CA_LONG, 99000, "99 milers"}, + {FORMAT_CA_LONG, 330000, "330 milers"}, + {FORMAT_CA_LONG, 3000.90, "3 miler"}, + {FORMAT_CA_LONG, 1000000, "1 mili\u00f3"}, + {FORMAT_CA_LONG, new BigInteger("12345678901234567890"), + "12345679 bilions"}, + {FORMAT_CA_LONG, new BigDecimal("12345678901234567890.89"), + "12345679 bilions"}, + {FORMAT_AS_LONG, 5000.0, "\u09eb \u09b9\u09be\u099c\u09be\u09f0"}, + {FORMAT_AS_LONG, 50000.0, "\u09eb\u09e6 \u09b9\u09be\u099c\u09be\u09f0"}, + {FORMAT_AS_LONG, 500000.0, "\u09eb \u09b2\u09be\u0996"}, + {FORMAT_AS_LONG, 5000000.0, "\u09eb \u09a8\u09bf\u09af\u09c1\u09a4"}, + {FORMAT_AS_LONG, 50000000.0, "\u09eb\u09e6 \u09a8\u09bf\u09af\u09c1\u09a4"}, + {FORMAT_AS_LONG, 500000000.0, "\u09eb\u09e6\u09e6 \u09a8\u09bf\u09af\u09c1\u09a4"}, + {FORMAT_AS_LONG, 5000000000.0, "\u09eb \u09b6\u09a4 \u0995\u09cb\u099f\u09bf"}, + {FORMAT_AS_LONG, 50000000000.0, "\u09eb\u09e6 \u09b6\u09a4 \u0995\u09cb\u099f\u09bf"}, + {FORMAT_AS_LONG, 500000000000.0, "\u09eb\u09e6\u09e6 \u09b6\u09a4 \u0995\u09cb\u099f\u09bf"}, + {FORMAT_AS_LONG, 5000000000000.0, "\u09eb \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"}, + {FORMAT_AS_LONG, 50000000000000.0, "\u09eb\u09e6 \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"}, + {FORMAT_AS_LONG, 500000000000000.0, "\u09eb\u09e6\u09e6 \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"}, + {FORMAT_AS_LONG, 5000000000000000.0, "\u09eb\u09e6\u09e6\u09e6 \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"}, + {FORMAT_AS_LONG, new BigInteger("12345678901234567890"), + "\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ef \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"}, + {FORMAT_AS_LONG, new BigDecimal("12345678901234567890123466767.89"), + "\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ee \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"}, + {FORMAT_BRX_SHORT, 999, "999"}, + {FORMAT_BRX_SHORT, 999.99, "1K"}, + {FORMAT_BRX_SHORT, 99000, "99K"}, + {FORMAT_BRX_SHORT, 330000, "330K"}, + {FORMAT_BRX_SHORT, 3000.90, "3K"}, + {FORMAT_BRX_SHORT, 1000000, "1M"}, + {FORMAT_BRX_SHORT, new BigInteger("12345678901234567890"), + "12345679T"}, + {FORMAT_BRX_SHORT, new BigDecimal("12345678901234567890.89"), + "12345679T"}, + // Less than 1000 no suffix + {FORMAT_SW_LONG, 499, "499"}, + // Boundary number + {FORMAT_SW_LONG, 1000, "elfu 1"}, + // Long path + {FORMAT_SW_LONG, 3000000L, "milioni 3"}, + // Long path, negative + {FORMAT_SW_LONG, -3000000L, "milioni -3"}, + // Double path + {FORMAT_SW_LONG, 3000000.0, "milioni 3"}, + // Double path, negative + {FORMAT_SW_LONG, -3000000.0, "milioni -3"}, + // BigInteger path + {FORMAT_SW_LONG, new BigInteger("12345678901234567890"), + "trilioni 12345679"}, + // BigDecimal path + {FORMAT_SW_LONG, new BigDecimal("12345678901234567890.89"), + "trilioni 12345679"}, + // Positives + // No compact form + {FORMAT_SE_SHORT, 999, "999"}, + // Long + {FORMAT_SE_SHORT, 8000000L, "8\u00a0mn"}, + // Double + {FORMAT_SE_SHORT, 8000.98, "8\u00a0dt"}, + // Big integer + {FORMAT_SE_SHORT, new BigInteger("12345678901234567890"), "12345679\u00a0bn"}, + // Big decimal + {FORMAT_SE_SHORT, new BigDecimal("12345678901234567890.98"), "12345679\u00a0bn"}, + // Negatives + // No compact form + {FORMAT_SE_SHORT, -999, "\u2212999"}, + // Long + {FORMAT_SE_SHORT, -8000000L, "\u22128\u00a0mn"}, + // Double + {FORMAT_SE_SHORT, -8000.98, "\u22128\u00a0dt"}, + // BigInteger + {FORMAT_SE_SHORT, new BigInteger("-12345678901234567890"), "\u221212345679\u00a0bn"}, + // BigDecimal + {FORMAT_SE_SHORT, new BigDecimal("-12345678901234567890.98"), "\u221212345679\u00a0bn"},}; + } + + @DataProvider(name = "parse") + Object[][] compactParseData() { + return new Object[][]{ + // compact number format instance, string to parse, parsed number, return type + {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F21", 1000L, Long.class}, + {FORMAT_DZ_LONG, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F23", -3000L, Long.class}, + {FORMAT_DZ_LONG, "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62" + + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21" + + "\u0F22\u0F23\u0F24\u0F25\u0F27", 1.23457E19, Double.class}, + {FORMAT_DZ_LONG, "-\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62" + + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21" + + "\u0F22\u0F23\u0F24\u0F25\u0F27", -1.23457E19, Double.class}, + {FORMAT_EN_US_SHORT, "-0.0", -0.0, Double.class}, + {FORMAT_EN_US_SHORT, "-0", -0.0, Double.class}, + {FORMAT_EN_US_SHORT, "0", 0L, Long.class}, + {FORMAT_EN_US_SHORT, "499", 499L, Long.class}, + {FORMAT_EN_US_SHORT, "-499", -499L, Long.class}, + {FORMAT_EN_US_SHORT, "499.89", 499.89, Double.class}, + {FORMAT_EN_US_SHORT, "-499.89", -499.89, Double.class}, + {FORMAT_EN_US_SHORT, "1K", 1000L, Long.class}, + {FORMAT_EN_US_SHORT, "-1K", -1000L, Long.class}, + {FORMAT_EN_US_SHORT, "3K", 3000L, Long.class}, + {FORMAT_EN_US_SHORT, "17K", 17000L, Long.class}, + {FORMAT_EN_US_SHORT, "-17K", -17000L, Long.class}, + {FORMAT_EN_US_SHORT, "-3K", -3000L, Long.class}, + {FORMAT_EN_US_SHORT, "12345678901234567890", 1.2345678901234567E19, Double.class}, + {FORMAT_EN_US_SHORT, "12345679T", 1.2345679E19, Double.class}, + {FORMAT_EN_US_SHORT, "-12345679T", -1.2345679E19, Double.class}, + {FORMAT_EN_US_SHORT, "599.01K", 599010L, Long.class}, + {FORMAT_EN_US_SHORT, "-599.01K", -599010L, Long.class}, + {FORMAT_EN_US_SHORT, "599444444.90T", 5.994444449E20, Double.class}, + {FORMAT_EN_US_SHORT, "-599444444.90T", -5.994444449E20, Double.class}, + {FORMAT_EN_US_SHORT, "123456789012345.5678K", 123456789012345568L, Long.class}, + {FORMAT_EN_US_SHORT, "17.000K", 17000L, Long.class}, + {FORMAT_EN_US_SHORT, "123.56678K", 123566.78000, Double.class}, + {FORMAT_EN_US_SHORT, "-123.56678K", -123566.78000, Double.class}, + {FORMAT_EN_LONG, "999", 999L, Long.class}, + {FORMAT_EN_LONG, "1 thousand", 1000L, Long.class}, + {FORMAT_EN_LONG, "3 thousand", 3000L, Long.class}, + {FORMAT_EN_LONG, "12345679 trillion", 1.2345679E19, Double.class}, + {FORMAT_HI_IN_LONG, "999", 999L, Long.class}, + {FORMAT_HI_IN_LONG, "-999", -999L, Long.class}, + {FORMAT_HI_IN_LONG, "1 \u0939\u091C\u093C\u093E\u0930", 1000L, Long.class}, + {FORMAT_HI_IN_LONG, "-1 \u0939\u091C\u093C\u093E\u0930", -1000L, Long.class}, + {FORMAT_HI_IN_LONG, "3 \u0939\u091C\u093C\u093E\u0930", 3000L, Long.class}, + {FORMAT_HI_IN_LONG, "12345679 \u0916\u0930\u092C", 1234567900000000000L, Long.class}, + {FORMAT_HI_IN_LONG, "-12345679 \u0916\u0930\u092C", -1234567900000000000L, Long.class}, + {FORMAT_JA_JP_SHORT, "-99", -99L, Long.class}, + {FORMAT_JA_JP_SHORT, "1\u4E07", 10000L, Long.class}, + {FORMAT_JA_JP_SHORT, "30\u4E07", 300000L, Long.class}, + {FORMAT_JA_JP_SHORT, "-30\u4E07", -300000L, Long.class}, + {FORMAT_JA_JP_SHORT, "12345679\u5146", 1.2345679E19, Double.class}, + {FORMAT_JA_JP_SHORT, "-12345679\u5146", -1.2345679E19, Double.class}, + {FORMAT_IT_SHORT, "-99", -99L, Long.class}, + {FORMAT_IT_SHORT, "1\u00a0Mln", 1000000L, Long.class}, + {FORMAT_IT_SHORT, "30\u00a0Mln", 30000000L, Long.class}, + {FORMAT_IT_SHORT, "-30\u00a0Mln", -30000000L, Long.class}, + {FORMAT_IT_SHORT, "12345679\u00a0Bln", 1.2345679E19, Double.class}, + {FORMAT_IT_SHORT, "-12345679\u00a0Bln", -1.2345679E19, Double.class}, + {FORMAT_SW_LONG, "-0.0", -0.0, Double.class}, + {FORMAT_SW_LONG, "499", 499L, Long.class}, + {FORMAT_SW_LONG, "elfu 1", 1000L, Long.class}, + {FORMAT_SW_LONG, "elfu 3", 3000L, Long.class}, + {FORMAT_SW_LONG, "elfu 17", 17000L, Long.class}, + {FORMAT_SW_LONG, "elfu -3", -3000L, Long.class}, + {FORMAT_SW_LONG, "499", 499L, Long.class}, + {FORMAT_SW_LONG, "-499", -499L, Long.class}, + {FORMAT_SW_LONG, "elfu 1", 1000L, Long.class}, + {FORMAT_SW_LONG, "elfu 3", 3000L, Long.class}, + {FORMAT_SW_LONG, "elfu -3", -3000L, Long.class}, + {FORMAT_SW_LONG, "elfu 17", 17000L, Long.class}, + {FORMAT_SW_LONG, "trilioni 12345679", 1.2345679E19, Double.class}, + {FORMAT_SW_LONG, "trilioni -12345679", -1.2345679E19, Double.class}, + {FORMAT_SW_LONG, "elfu 599.01", 599010L, Long.class}, + {FORMAT_SW_LONG, "elfu -599.01", -599010L, Long.class}, + {FORMAT_SE_SHORT, "999", 999L, Long.class}, + {FORMAT_SE_SHORT, "8\u00a0mn", 8000000L, Long.class}, + {FORMAT_SE_SHORT, "8\u00a0dt", 8000L, Long.class}, + {FORMAT_SE_SHORT, "12345679\u00a0bn", 1.2345679E19, Double.class}, + {FORMAT_SE_SHORT, "12345679,89\u00a0bn", 1.2345679890000001E19, Double.class}, + {FORMAT_SE_SHORT, "\u2212999", -999L, Long.class}, + {FORMAT_SE_SHORT, "\u22128\u00a0mn", -8000000L, Long.class}, + {FORMAT_SE_SHORT, "\u22128\u00a0dt", -8000L, Long.class}, + {FORMAT_SE_SHORT, "\u221212345679\u00a0bn", -1.2345679E19, Double.class}, + {FORMAT_SE_SHORT, "\u221212345679,89\u00a0bn", -1.2345679890000001E19, Double.class},}; + } + + @DataProvider(name = "exceptionParse") + Object[][] exceptionParseData() { + return new Object[][]{ + // compact number instance, string to parse, null (no o/p; must throws exception) + // no number + {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42", null}, + // Invalid prefix + {FORMAT_DZ_LONG, "-\u0F66\u0F9F\u0F7C\u0F44,\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F23", null}, + // Invalid prefix for en_US + {FORMAT_EN_US_SHORT, "K12,347", null}, + // Invalid prefix for ja_JP + {FORMAT_JA_JP_SHORT, "\u4E071", null}, + // Localized minus sign should be used + {FORMAT_SE_SHORT, "-8\u00a0mn", null},}; + } + + @DataProvider(name = "invalidParse") + Object[][] invalidParseData() { + return new Object[][]{ + // compact number instance, string to parse, parsed number + // Prefix and suffix do not match + {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F21 KM", 1000L}, + // Exponents are unparseable + {FORMAT_EN_US_SHORT, "-1.05E4K", -1.05}, + // Default instance does not allow grouping + {FORMAT_EN_US_SHORT, "12,347", 12L}, + // Take partial suffix "K" as 1000 for en_US_SHORT patterns + {FORMAT_EN_US_SHORT, "12KM", 12000L}, + // Invalid suffix + {FORMAT_HI_IN_LONG, "-1 \u00a0\u0915.", -1L},}; + } + + @DataProvider(name = "fieldPosition") + Object[][] formatFieldPositionData() { + return new Object[][]{ + //compact number instance, number to format, field, start position, end position, formatted string + {FORMAT_DZ_LONG, -3500, NumberFormat.Field.SIGN, 0, 1, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"}, + {FORMAT_DZ_LONG, 3500, NumberFormat.Field.INTEGER, 9, 10, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"}, + {FORMAT_DZ_LONG, -3500, NumberFormat.Field.INTEGER, 10, 11, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"}, + {FORMAT_DZ_LONG, 999, NumberFormat.Field.INTEGER, 0, 3, "\u0F29\u0F29\u0F29"}, + {FORMAT_DZ_LONG, -999, NumberFormat.Field.INTEGER, 1, 4, "-\u0F29\u0F29\u0F29"}, + {FORMAT_DZ_LONG, 3500, NumberFormat.Field.PREFIX, 0, 9, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"}, + {FORMAT_DZ_LONG, -3500, NumberFormat.Field.PREFIX, 0, 10, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"}, + {FORMAT_DZ_LONG, 999, NumberFormat.Field.PREFIX, 0, 0, "\u0F29\u0F29\u0F29"}, + {FORMAT_EN_US_SHORT, -3500, NumberFormat.Field.SIGN, 0, 1, "-4K"}, + {FORMAT_EN_US_SHORT, 3500, NumberFormat.Field.INTEGER, 0, 1, "4K"}, + {FORMAT_EN_US_SHORT, 14900000067L, NumberFormat.Field.INTEGER, 0, 2, "15B"}, + {FORMAT_EN_US_SHORT, -1000, NumberFormat.Field.PREFIX, 0, 1, "-1K"}, + {FORMAT_EN_US_SHORT, 3500, NumberFormat.Field.SUFFIX, 1, 2, "4K"}, + {FORMAT_EN_US_SHORT, 14900000067L, NumberFormat.Field.SUFFIX, 2, 3, "15B"}, + {FORMAT_EN_LONG, 3500, NumberFormat.Field.INTEGER, 0, 1, "4 thousand"}, + {FORMAT_EN_LONG, 14900000067L, NumberFormat.Field.INTEGER, 0, 2, "15 billion"}, + {FORMAT_EN_LONG, 3500, NumberFormat.Field.SUFFIX, 1, 10, "4 thousand"}, + {FORMAT_EN_LONG, 14900000067L, NumberFormat.Field.SUFFIX, 2, 10, "15 billion"}, + {FORMAT_JA_JP_SHORT, 14900000067L, NumberFormat.Field.INTEGER, 0, 3, "149\u5104"}, + {FORMAT_JA_JP_SHORT, -999.99, NumberFormat.Field.INTEGER, 1, 6, "-1,000"}, + {FORMAT_JA_JP_SHORT, 14900000067L, NumberFormat.Field.SUFFIX, 3, 4, "149\u5104"}, + {FORMAT_JA_JP_SHORT, -999.99, NumberFormat.Field.SUFFIX, 0, 0, "-1,000"}, + {FORMAT_JA_JP_SHORT, -999.99, NumberFormat.Field.SIGN, 0, 1, "-1,000"}, + {FORMAT_HI_IN_LONG, -14900000067L, NumberFormat.Field.SIGN, 0, 1, + "-15 \u0905\u0930\u092C"}, + {FORMAT_HI_IN_LONG, 3500, NumberFormat.Field.INTEGER, 0, 1, + "4 \u0939\u091C\u093C\u093E\u0930"}, + {FORMAT_HI_IN_LONG, 14900000067L, NumberFormat.Field.INTEGER, 0, 2, + "15 \u0905\u0930\u092C"}, + {FORMAT_HI_IN_LONG, 3500, NumberFormat.Field.SUFFIX, 1, 7, + "4 \u0939\u091C\u093C\u093E\u0930"}, + {FORMAT_HI_IN_LONG, 14900000067L, NumberFormat.Field.SUFFIX, 2, 6, + "15 \u0905\u0930\u092C"}, + {FORMAT_SE_SHORT, 8000000L, NumberFormat.Field.SUFFIX, 1, 4, "8\u00a0mn"}, + {FORMAT_SE_SHORT, 8000.98, NumberFormat.Field.SUFFIX, 1, 4, "8\u00a0dt"}, + {FORMAT_SE_SHORT, new BigInteger("12345678901234567890"), NumberFormat.Field.SUFFIX, 8, 11, "12345679\u00a0bn"}, + {FORMAT_SE_SHORT, new BigDecimal("12345678901234567890.98"), NumberFormat.Field.SUFFIX, 8, 11, "12345679\u00a0bn"}, + {FORMAT_SE_SHORT, -8000000L, NumberFormat.Field.INTEGER, 1, 2, "\u22128\u00a0mn"}, + {FORMAT_SE_SHORT, -8000.98, NumberFormat.Field.SIGN, 0, 1, "\u22128\u00a0dt"}, + {FORMAT_SE_SHORT, new BigDecimal("-48982865901234567890.98"), NumberFormat.Field.INTEGER, 1, 9, "\u221248982866\u00a0bn"},}; + } + + @DataProvider(name = "varParsePosition") + Object[][] varParsePosition() { + return new Object[][]{ + // compact number instance, parse string, parsed number, + // start position, end position, error index + {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F21 KM", 1000L, 0, 10, -1}, + // Invalid prefix returns null + {FORMAT_DZ_LONG, "Number is: -\u0F66\u0F9F\u0F7C\u0F44,\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F23", null, 11, 11, 11}, + // Returns null + {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42", null, 0, 0, 0}, + {FORMAT_EN_US_SHORT, "Exponent: -1.05E4K", -1.05, 10, 15, -1}, + // Default instance does not allow grouping + {FORMAT_EN_US_SHORT, "12,347", 12L, 0, 2, -1}, + // Invalid suffix "KM" for en_US_SHORT patterns + {FORMAT_EN_US_SHORT, "12KM", 12000L, 0, 3, -1}, + // Invalid suffix + {FORMAT_HI_IN_LONG, "-1 \u00a0\u0915.", -1L, 0, 2, -1}, + {FORMAT_EN_LONG, "Number is: 12345679 trillion", + 1.2345679E19, 11, 28, -1}, + {FORMAT_EN_LONG, "Number is: -12345679 trillion", + -1.2345679E19, 11, 29, -1}, + {FORMAT_EN_LONG, "parse 12 thousand and four", 12000L, 6, 17, -1},}; + } + + @Test + public void testInstanceCreation() { + Stream.of(NumberFormat.getAvailableLocales()).forEach(l -> NumberFormat + .getCompactNumberInstance(l, NumberFormat.Style.SHORT).format(10000)); + Stream.of(NumberFormat.getAvailableLocales()).forEach(l -> NumberFormat + .getCompactNumberInstance(l, NumberFormat.Style.LONG).format(10000)); + } + + @Test(dataProvider = "format") + public void testFormat(NumberFormat cnf, Object number, + String expected) { + CompactFormatAndParseHelper.testFormat(cnf, number, expected); + } + + @Test(dataProvider = "parse") + public void testParse(NumberFormat cnf, String parseString, + Number expected, Class returnType) throws ParseException { + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, returnType); + } + + @Test(dataProvider = "parse") + public void testParsePosition(NumberFormat cnf, String parseString, + Number expected, Class returnType) throws ParseException { + ParsePosition pos = new ParsePosition(0); + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, pos, returnType); + assertEquals(pos.getIndex(), parseString.length()); + assertEquals(pos.getErrorIndex(), -1); + } + + @Test(dataProvider = "varParsePosition") + public void testVarParsePosition(NumberFormat cnf, String parseString, + Number expected, int startPosition, int indexPosition, + int errPosition) throws ParseException { + ParsePosition pos = new ParsePosition(startPosition); + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, pos, null); + assertEquals(pos.getIndex(), indexPosition); + assertEquals(pos.getErrorIndex(), errPosition); + } + + @Test(dataProvider = "exceptionParse", expectedExceptions = ParseException.class) + public void throwsParseException(NumberFormat cnf, String parseString, + Number expected) throws ParseException { + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null); + } + + @Test(dataProvider = "invalidParse") + public void testInvalidParse(NumberFormat cnf, String parseString, + Number expected) throws ParseException { + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null); + } + + @Test(dataProvider = "fieldPosition") + public void testFormatWithFieldPosition(NumberFormat nf, + Object number, Format.Field field, int posStartExpected, + int posEndExpected, String expected) { + FieldPosition pos = new FieldPosition(field); + StringBuffer buf = new StringBuffer(); + StringBuffer result = nf.format(number, buf, pos); + assertEquals(result.toString(), expected, "Incorrect formatting of the number '" + + number + "'"); + assertEquals(pos.getBeginIndex(), posStartExpected, "Incorrect start position" + + " while formatting the number '" + number + "', for the field " + field); + assertEquals(pos.getEndIndex(), posEndExpected, "Incorrect end position" + + " while formatting the number '" + number + "', for the field " + field); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestCompactPatternsValidity.java 2018-11-28 14:09:14.374276887 +0530 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the validity of compact number patterns specified through + * CompactNumberFormat constructor + * @run testng/othervm TestCompactPatternsValidity + */ + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.CompactNumberFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.List; +import java.util.Locale; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestCompactPatternsValidity { + + // Max range 10^4 + private static final String[] COMPACT_PATTERN1 = new String[]{"0", "0", "0", "0K", "00K"}; + // Quoted special character '.' as prefix + private static final String[] COMPACT_PATTERN2 = new String[]{"0", "'.'K0"}; + // Quoted special character '.' as suffix + private static final String[] COMPACT_PATTERN3 = new String[]{"0", "0", "0", "0K", "00K'.'"}; + // Containing both prefix and suffix + private static final String[] COMPACT_PATTERN4 = new String[]{"", "", "H0H", "0K", "00K", "H0G"}; + // Differing while specifying prefix and suffix + private static final String[] COMPACT_PATTERN5 = new String[]{"", "", "", "0K", "K0"}; + // Containing both prefix ('.') and suffix (K) + private static final String[] COMPACT_PATTERN6 = new String[]{"0", "", "", "'.'0K"}; + // Quoted special character ',' as suffix + private static final String[] COMPACT_PATTERN7 = new String[]{"", "0", "0", "0K','"}; + // Most commonly used type of compact patterns with 15 elements + private static final String[] COMPACT_PATTERN8 = new String[]{"", "", "", "0K", "00K", "000K", "0M", + "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"}; + // All empty or special patterns; checking the default formatting behaviour + private static final String[] COMPACT_PATTERN9 = new String[]{"", "", "", "0", "0", "", "", "", "", "", "", "", "", "", ""}; + // Patterns beyond 10^19; divisors beyond long range + private static final String[] COMPACT_PATTERN10 = new String[]{"", "", "", "0K", "00K", "000K", "0M", "00M", + "000M", "0B", "00B", "000B", "0T", "00T", "000T", "0L", "00L", "000L", "0XL", "00XL"}; + // Containing positive;negative subpatterns + private static final String[] COMPACT_PATTERN11 = new String[]{"", "", "", "elfu 0;elfu -0", "elfu 00;elfu -00", + "elfu 000;elfu -000", "milioni 0;milioni -0", "milioni 00;milioni -00", "milioni 000;milioni -000"}; + // Containing both prefix and suffix and positive;negative subpatern + private static final String[] COMPACT_PATTERN12 = new String[]{"", "", "H0H;H-0H", "0K;0K-", "00K;-00K", "H0G;-H0G"}; + + @DataProvider(name = "invalidPatterns") + Object[][] invalidCompactPatterns() { + return new Object[][]{ + // compact patterns + // Pattern containing unquoted special character '.' + {new String[]{"", "", "", "0K", "0.0K"}}, + // A non empty pattern containing no 0s (min integer digits) + {new String[]{"K", "0K", "00K"}}, + // 0s (min integer digits) exceeding for the range at index 3 + {new String[]{"", "", "0K", "00000K"}},}; + } + + @DataProvider(name = "validPatternsFormat") + Object[][] validPatternsFormat() { + return new Object[][]{ + // compact patterns, numbers, expected output + {COMPACT_PATTERN1, List.of(200, 1000, 3000, 500000), List.of("200", "1K", "3K", "500K")}, + {COMPACT_PATTERN2, List.of(1, 20, 3000), List.of("1", ".K2", ".K300")}, + {COMPACT_PATTERN3, List.of(100.99, 1000, 30000), List.of("101", "1K", "30K.")}, + {COMPACT_PATTERN4, List.of(0.0, 500, -500, 30000, 5000000), List.of("0", "H5H", "-H5H", "30K", "H50G")}, + {COMPACT_PATTERN5, List.of(100, 1000, 30000), List.of("100", "1K", "K3")}, + {COMPACT_PATTERN6, List.of(20.99, 1000, 30000), List.of("21", ".1K", ".30K")}, + {COMPACT_PATTERN7, List.of(100, 1000, new BigInteger("12345678987654321")), List.of("100", "1K,", "12345678987654K,")}, + {COMPACT_PATTERN8, List.of(new BigInteger("223565686837667632"), new BigDecimal("12322456774334.89766"), 30000, 3456.78), + List.of("223566T", "12T", "30K", "3K")}, + {COMPACT_PATTERN9, List.of(new BigInteger("223566000000000000"), new BigDecimal("12345678987654567"), 30000, 3000), + List.of("223,566,000,000,000,000", "12,345,678,987,654,567", "30,000", "3,000")}, + {COMPACT_PATTERN10, List.of(new BigInteger("100000000000000000"), new BigInteger("10000000000000000000"), new BigDecimal("555555555555555555555.89766"), 30000), + List.of("100L", "10XL", "556XL", "30K")}, + {COMPACT_PATTERN11, List.of(20.99, -20.99, 1000, -1000, 30000, -30000, new BigInteger("12345678987654321"), new BigInteger("-12345678987654321")), + List.of("21", "-21", "elfu 1", "elfu -1", "elfu 30", "elfu -30", "milioni 12345678988", "milioni -12345678988")}, + {COMPACT_PATTERN12, List.of(0, 500, -500, 30000, -3000, 5000000), List.of("0", "H5H", "H-5H", "30K", "3K-", "H50G")},}; + } + + @DataProvider(name = "validPatternsParse") + Object[][] validPatternsParse() { + return new Object[][]{ + // compact patterns, parse string, expected output + {COMPACT_PATTERN1, List.of(".56", "200", ".1K", "3K", "500K"), List.of(0.56, 200L, 100L, 3000L, 500000L)}, + {COMPACT_PATTERN2, List.of("1", ".K2", ".K300"), List.of(1L, 20L, 3000L)}, + {COMPACT_PATTERN3, List.of("101", "1K", "30K."), List.of(101L, 1000L, 30000L)}, + {COMPACT_PATTERN4, List.of("0", "H5H", "-H5H", "30K", "H50G"), List.of(0L, 500L, -500L, 30000L, 5000000L)}, + {COMPACT_PATTERN5, List.of("100", "1K", "K3"), List.of(100L, 1000L, 30000L)}, + {COMPACT_PATTERN6, List.of("21", ".1K", ".30K"), List.of(21L, 1000L, 30000L)}, + {COMPACT_PATTERN7, List.of("100", "1K,", "12345678987654K,"), List.of(100L, 1000L, 12345678987654000L)}, + {COMPACT_PATTERN8, List.of("223566T", "12T", "30K", "3K"), List.of(223566000000000000L, 12000000000000L, 30000L, 3000L)}, + {COMPACT_PATTERN10, List.of("1L", "100L", "10XL", "556XL", "30K"), List.of(1000000000000000L, 100000000000000000L, 1.0E19, 5.56E20, 30000L)}, + {COMPACT_PATTERN11, List.of("21", "-21", "100.90", "-100.90", "elfu 1", "elfu -1", "elfu 30", "elfu -30", "milioni 12345678988", "milioni -12345678988"), + List.of(21L, -21L, 100.90, -100.90, 1000L, -1000L, 30000L, -30000L, 12345678988000000L, -12345678988000000L)}, + {COMPACT_PATTERN12, List.of("0", "H5H", "H-5H", "30K", "30K-", "H50G"), List.of(0L, 500L, -500L, 30000L, -30000L, 5000000L)},}; + } + + @Test(dataProvider = "invalidPatterns", + expectedExceptions = RuntimeException.class) + public void testInvalidCompactPatterns(String[] compactPatterns) { + new CompactNumberFormat("#,##0.0#", DecimalFormatSymbols + .getInstance(Locale.US), compactPatterns); + } + + @Test(dataProvider = "validPatternsFormat") + public void testValidPatternsFormat(String[] compactPatterns, + List numbers, List expected) { + CompactNumberFormat fmt = new CompactNumberFormat("#,##0.0#", + DecimalFormatSymbols.getInstance(Locale.US), compactPatterns); + for (int index = 0; index < numbers.size(); index++) { + CompactFormatAndParseHelper.testFormat(fmt, numbers.get(index), + expected.get(index)); + } + } + + @Test(dataProvider = "validPatternsParse") + public void testValidPatternsParse(String[] compactPatterns, + List parseString, List numbers) throws ParseException { + CompactNumberFormat fmt = new CompactNumberFormat("#,##0.0#", + DecimalFormatSymbols.getInstance(Locale.US), compactPatterns); + for (int index = 0; index < parseString.size(); index++) { + CompactFormatAndParseHelper.testParse(fmt, parseString.get(index), + numbers.get(index), null, null); + } + } +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestEquality.java 2018-11-28 14:09:15.142276887 +0530 @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the equals and hashCode method of CompactNumberFormat + * @modules jdk.localedata + * @run testng/othervm TestEquality + * + */ + +import java.text.CompactNumberFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; +import org.testng.annotations.Test; + +public class TestEquality { + + @Test + public void testEquality() { + CompactNumberFormat cnf1 = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + + CompactNumberFormat cnf2 = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + + // A custom compact instance with the same state as + // compact number instance of "en_US" locale with SHORT style + String decimalPattern = "#,##0.###"; + String[] compactPatterns = new String[]{"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"}; + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Locale.US); + CompactNumberFormat cnf3 = new CompactNumberFormat(decimalPattern, symbols, compactPatterns); + + // A compact instance created with different decimalPattern than cnf3 + CompactNumberFormat cnf4 = new CompactNumberFormat("#,#0.0#", symbols, compactPatterns); + + // A compact instance created with different format symbols than cnf3 + CompactNumberFormat cnf5 = new CompactNumberFormat(decimalPattern, + DecimalFormatSymbols.getInstance(Locale.JAPAN), compactPatterns); + + // A compact instance created with different compact patterns than cnf3 + CompactNumberFormat cnf6 = new CompactNumberFormat(decimalPattern, + symbols, new String[]{"", "", "", "0K", "00K", "000K"}); + + // Checking reflexivity + if (!cnf1.equals(cnf1)) { + throw new RuntimeException("[testEquality() reflexivity FAILED: The compared" + + " objects must be equal]"); + } + + // Checking symmetry, checking equality of two same objects + if (!cnf1.equals(cnf2) || !cnf2.equals(cnf1)) { + throw new RuntimeException("[testEquality() symmetry FAILED: The compared" + + " objects must be equal]"); + } + + // Checking transitivity, three objects must be equal + if (!cnf1.equals(cnf2) || !cnf2.equals(cnf3) || !cnf1.equals(cnf3)) { + throw new RuntimeException("[testEquality() transitivity FAILED: The compared" + + " objects must be equal]"); + } + + // Objects must not be equal as the decimalPattern is different + checkEquals(cnf3, cnf4, false, "1st", "different decimal pattern"); + + // Objects must not be equal as the format symbols instance is different + checkEquals(cnf3, cnf5, false, "2nd", "different format symbols"); + + // Objects must not be equal as the compact patters are different + checkEquals(cnf3, cnf6, false, "3rd", "different compact patterns"); + + // Changing the min integer digits of first object; objects must not + // be equal + cnf1.setMinimumIntegerDigits(5); + checkEquals(cnf1, cnf2, false, "4th", "different min integer digits"); + + // Changing the min integer digits of second object; objects must + // be equal + cnf2.setMinimumIntegerDigits(5); + checkEquals(cnf1, cnf2, true, "5th", ""); + + // Changing the grouping size of first object; objects must not + // be equal + cnf1.setGroupingSize(4); + checkEquals(cnf1, cnf2, false, "6th", "different grouping size"); + + // Changing the grouping size if second object; objects must be equal + cnf2.setGroupingSize(4); + checkEquals(cnf1, cnf2, true, "7th", ""); + + // Changing the parseBigDecimal of first object; objects must not + // be equal + cnf1.setParseBigDecimal(true); + checkEquals(cnf1, cnf2, false, "8th", "different parse big decimal"); + + } + + private void checkEquals(CompactNumberFormat cnf1, CompactNumberFormat cnf2, + boolean mustEqual, String nthComparison, String message) { + if (cnf1.equals(cnf2) != mustEqual) { + if (mustEqual) { + throw new RuntimeException("[testEquality() " + nthComparison + + " comparison FAILED: The compared objects must be equal]"); + } else { + throw new RuntimeException("[testEquality() " + nthComparison + + " comparison FAILED: The compared objects must" + + " not be equal because of " + message + "]"); + } + } + } + + @Test + public void testHashCode() { + NumberFormat cnf1 = NumberFormat + .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT); + NumberFormat cnf2 = NumberFormat + .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT); + + if (cnf1.hashCode() != cnf2.hashCode()) { + throw new RuntimeException("[testHashCode() FAILED: hashCode of the" + + " compared objects must match]"); + } + } + + // Test the property of equals and hashCode i.e. two equal object must + // always have the same hashCode + @Test + public void testEqualsAndHashCode() { + NumberFormat cnf1 = NumberFormat + .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.SHORT); + cnf1.setMinimumIntegerDigits(5); + NumberFormat cnf2 = NumberFormat + .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.SHORT); + cnf2.setMinimumIntegerDigits(5); + if (cnf1.equals(cnf2)) { + if (cnf1.hashCode() != cnf2.hashCode()) { + throw new RuntimeException("[testEqualsAndHashCode() FAILED: two" + + " equal objects must have same hashCode]"); + } + } else { + throw new RuntimeException("[testEqualsAndHashCode() FAILED: The" + + " compared objects must be equal]"); + } + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestFormatToCharacterIterator.java 2018-11-28 14:09:15.970276887 +0530 @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the functioning of + * CompactNumberFormat.formatToCharacterIterator method + * @modules jdk.localedata + * @run testng/othervm TestFormatToCharacterIterator + */ +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.AttributedCharacterIterator; +import java.text.CharacterIterator; +import java.text.Format; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Set; +import static org.testng.Assert.assertEquals; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestFormatToCharacterIterator { + + private static final NumberFormat FORMAT_DZ = NumberFormat + .getCompactNumberInstance(new Locale("dz"), + NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_EN_US = NumberFormat + .getCompactNumberInstance(Locale.US, + NumberFormat.Style.SHORT); + + private static final NumberFormat FORMAT_EN_LONG = NumberFormat + .getCompactNumberInstance(new Locale("en"), + NumberFormat.Style.LONG); + + @DataProvider(name = "fieldPositions") + Object[][] compactFieldPositionData() { + return new Object[][]{ + // compact format instance, number, resulted string, attributes/fields, attribute positions + {FORMAT_DZ, 1000.09, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F21", + new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 9, 9, 10}}, + {FORMAT_DZ, -999.99, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F21", + new Format.Field[]{NumberFormat.Field.SIGN, NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, + new int[]{0, 1, 1, 10, 10, 11}}, + {FORMAT_DZ, -0.0, "-\u0F20", new Format.Field[]{NumberFormat.Field.SIGN, NumberFormat.Field.INTEGER}, new int[]{0, 1, 1, 2}}, + {FORMAT_DZ, 3000L, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F23", + new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 9, 9, 10}}, + {FORMAT_DZ, new BigInteger("12345678901234567890"), + "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27", + new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 14, 14, 20}}, + {FORMAT_DZ, new BigDecimal("12345678901234567890.89"), + "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27", + new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 14, 14, 20}}, + // Zeros + {FORMAT_EN_US, 0, "0", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 1}}, + {FORMAT_EN_US, 0.0, "0", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 1}}, + {FORMAT_EN_US, -0.0, "-0", new Format.Field[]{NumberFormat.Field.SIGN, NumberFormat.Field.INTEGER}, new int[]{0, 1, 1, 2}}, + // Less than 1000 no suffix + {FORMAT_EN_US, 499, "499", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 3}}, + // Boundary number + {FORMAT_EN_US, 1000.0, "1K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}}, + // Long + {FORMAT_EN_US, 3000L, "3K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}}, + {FORMAT_EN_US, 30000L, "30K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}}, + {FORMAT_EN_US, 300000L, "300K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}}, + {FORMAT_EN_US, 3000000L, "3M", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}}, + {FORMAT_EN_US, 30000000L, "30M", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}}, + {FORMAT_EN_US, 300000000L, "300M", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}}, + {FORMAT_EN_US, 3000000000L, "3B", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}}, + {FORMAT_EN_US, 30000000000L, "30B", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}}, + {FORMAT_EN_US, 300000000000L, "300B", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}}, + {FORMAT_EN_US, 3000000000000L, "3T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}}, + {FORMAT_EN_US, 30000000000000L, "30T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}}, + {FORMAT_EN_US, 300000000000000L, "300T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}}, + {FORMAT_EN_US, 3000000000000000L, "3000T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 4, 4, 5}}, + // Double + {FORMAT_EN_US, 3000.0, "3K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}}, + {FORMAT_EN_US, 30000.0, "30K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}}, + {FORMAT_EN_US, 300000.0, "300K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}}, + {FORMAT_EN_US, 3000000000000000.0, "3000T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 4, 4, 5}}, + // BigInteger + {FORMAT_EN_US, new BigInteger("12345678901234567890"), "12345679T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 9}}, + // BigDecimal + {FORMAT_EN_US, new BigDecimal("12345678901234567890.89"), "12345679T", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 9}}, + // Number as exponent + {FORMAT_EN_US, 9.78313E+3, "10K", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}}, + // Less than 1000 no suffix + {FORMAT_EN_LONG, 999, "999", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 3}}, + // Round the value and then format + {FORMAT_EN_LONG, 999.99, "1 thousand", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 10}}, + // 10 thousand + {FORMAT_EN_LONG, 99000, "99 thousand", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 11}}, + // Long path + {FORMAT_EN_LONG, 330000, "330 thousand", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 12}}, + // Double path + {FORMAT_EN_LONG, 3000.90, "3 thousand", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 10}}, + // BigInteger path + {FORMAT_EN_LONG, new BigInteger("12345678901234567890"), "12345679 trillion", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 17}}, + // BigDecimal path + {FORMAT_EN_LONG, new BigDecimal("12345678901234567890.89"), "12345679 trillion", + new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 17}} + }; + } + + @Test(dataProvider = "fieldPositions") + public void testFormatToCharacterIterator(NumberFormat fmt, Object number, + String expected, Format.Field[] expectedFields, int[] positions) { + AttributedCharacterIterator iterator = fmt.formatToCharacterIterator(number); + assertEquals(getText(iterator), expected, "Incorrect formatting of the number '" + + number + "'"); + + iterator.first(); + // Check start and end index of the formatted string + assertEquals(iterator.getBeginIndex(), 0, "Incorrect start index: " + + iterator.getBeginIndex() + " of the formatted string: " + expected); + assertEquals(iterator.getEndIndex(), expected.length(), "Incorrect end index: " + + iterator.getEndIndex() + " of the formatted string: " + expected); + + // Check the attributes returned by the formatToCharacterIterator + assertEquals(iterator.getAllAttributeKeys(), Set.of(expectedFields), + "Attributes do not match while formatting number: " + number); + + // Check the begin and end index for attributes + iterator.first(); + int currentPosition = 0; + do { + int start = iterator.getRunStart(); + int end = iterator.getRunLimit(); + assertEquals(start, positions[currentPosition], + "Incorrect start position for the attribute(s): " + + iterator.getAttributes().keySet()); + assertEquals(end, positions[currentPosition + 1], + "Incorrect end position for the attribute(s): " + + iterator.getAttributes().keySet()); + currentPosition = currentPosition + 2; + iterator.setIndex(end); + } while (iterator.current() != CharacterIterator.DONE); + } + + // Create the formatted string from returned AttributedCharacterIterator + private String getText(AttributedCharacterIterator iterator) { + StringBuffer buffer = new StringBuffer(); + for (char c = iterator.first(); c != CharacterIterator.DONE; + c = iterator.next()) { + buffer.append(c); + } + return buffer.toString(); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestMutatingInstance.java 2018-11-28 14:09:16.742276887 +0530 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the functioning of compact number format by changing the + * formatting parameters. For example, min fraction digits, grouping + * size etc. + * @modules jdk.localedata + * @run testng/othervm TestMutatingInstance + */ +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.CompactNumberFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestMutatingInstance { + + private static final NumberFormat FORMAT_FRACTION = NumberFormat + .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG); + + private static final CompactNumberFormat FORMAT_GROUPING = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_MININTEGER = NumberFormat + .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG); + + private static final NumberFormat FORMAT_PARSEINTONLY = NumberFormat + .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG); + + // No compact patterns are specified for this instance except at index 4. + // This is to test how the behaviour differs between compact number formatting + // and general number formatting + private static final NumberFormat FORMAT_NO_PATTERNS = new CompactNumberFormat( + "#,##0.0#", DecimalFormatSymbols.getInstance(Locale.US), + new String[]{"", "", "", "", "00K", "", "", "", "", "", "", "", "", "", ""}); + + @BeforeTest + public void mutateInstances() { + FORMAT_FRACTION.setMinimumFractionDigits(2); + FORMAT_GROUPING.setGroupingSize(3); + FORMAT_GROUPING.setGroupingUsed(true); + FORMAT_MININTEGER.setMinimumIntegerDigits(5); + FORMAT_PARSEINTONLY.setParseIntegerOnly(true); + FORMAT_PARSEINTONLY.setGroupingUsed(true); + // Setting min fraction digits and other fields does not effect + // the general number formatting behaviour, when no compact number + // patterns are specified + FORMAT_NO_PATTERNS.setMinimumFractionDigits(2); + } + + @DataProvider(name = "format") + Object[][] compactFormatData() { + return new Object[][]{ + {FORMAT_FRACTION, 1900, "1.90 thousand"}, + {FORMAT_FRACTION, 1000, "1.00 thousand"}, + {FORMAT_FRACTION, 9090.99, "9.09 thousand"}, + {FORMAT_FRACTION, new BigDecimal(12346567890987654.32), + "12346.57 trillion"}, + {FORMAT_FRACTION, new BigInteger("12346567890987654"), + "12346.57 trillion"}, + {FORMAT_GROUPING, new BigDecimal(12346567890987654.32), + "12,347 trillion"}, + {FORMAT_GROUPING, 100000, "100 thousand"}, + {FORMAT_MININTEGER, 10000, "00010 thousand"}, + {FORMAT_NO_PATTERNS, 100000, "100,000"}, + {FORMAT_NO_PATTERNS, 1000.998, "1,001"}, + {FORMAT_NO_PATTERNS, 10900, "10.90K"}, + {FORMAT_NO_PATTERNS, new BigDecimal(12346567890987654.32), "12,346,567,890,987,654"},}; + } + + @DataProvider(name = "parse") + Object[][] compactParseData() { + return new Object[][]{ + {FORMAT_FRACTION, "190 thousand", 190000L}, + {FORMAT_FRACTION, "19.9 thousand", 19900L}, + {FORMAT_GROUPING, "12,346 thousand", 12346000L}, + {FORMAT_PARSEINTONLY, "12345 thousand", 12345000L}, + {FORMAT_PARSEINTONLY, "12,345 thousand", 12345000L}, + {FORMAT_PARSEINTONLY, "12.345 thousand", 12000L},}; + } + + @Test(dataProvider = "format") + public void formatCompactNumber(NumberFormat nf, + Object number, String expected) { + CompactFormatAndParseHelper.testFormat(nf, number, expected); + } + + @Test(dataProvider = "parse") + public void parseCompactNumber(NumberFormat nf, + String parseString, Number expected) throws ParseException { + CompactFormatAndParseHelper.testParse(nf, parseString, expected, null, null); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestParseBigDecimal.java 2018-11-28 14:09:17.474276887 +0530 @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks CNF.parse() when parseBigDecimal is set to true + * @modules jdk.localedata + * @run testng/othervm TestParseBigDecimal + */ + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.text.CompactNumberFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; + +public class TestParseBigDecimal { + + private static final CompactNumberFormat FORMAT_DZ_LONG = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("dz"), NumberFormat.Style.LONG); + + private static final CompactNumberFormat FORMAT_EN_US_SHORT = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + + private static final CompactNumberFormat FORMAT_EN_LONG = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG); + + private static final CompactNumberFormat FORMAT_HI_IN_LONG = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.LONG); + + private static final CompactNumberFormat FORMAT_JA_JP_SHORT = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT); + + private static final CompactNumberFormat FORMAT_IT_SHORT = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("it"), NumberFormat.Style.SHORT); + + private static final CompactNumberFormat FORMAT_SW_LONG = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("sw"), NumberFormat.Style.LONG); + + private static final CompactNumberFormat FORMAT_SE_SHORT = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(new Locale("se"), NumberFormat.Style.SHORT); + + @BeforeTest + public void mutateInstances() { + FORMAT_DZ_LONG.setParseBigDecimal(true); + FORMAT_EN_US_SHORT.setParseBigDecimal(true); + FORMAT_EN_LONG.setParseBigDecimal(true); + FORMAT_HI_IN_LONG.setParseBigDecimal(true); + FORMAT_JA_JP_SHORT.setParseBigDecimal(true); + FORMAT_IT_SHORT.setParseBigDecimal(true); + FORMAT_SW_LONG.setParseBigDecimal(true); + FORMAT_SE_SHORT.setParseBigDecimal(true); + } + + @DataProvider(name = "parse") + Object[][] compactParseData() { + return new Object[][]{ + // compact number format instance, string to parse, parsed number + {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F21", new BigDecimal("1000")}, + {FORMAT_DZ_LONG, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2" + + "\u0F42 \u0F23", new BigDecimal("-3000")}, + {FORMAT_DZ_LONG, "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62" + + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21" + + "\u0F22\u0F23\u0F24\u0F25\u0F27", new BigDecimal("12345700000000000000")}, + {FORMAT_DZ_LONG, "-\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62" + + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21" + + "\u0F22\u0F23\u0F24\u0F25\u0F27", new BigDecimal("-12345700000000000000")}, + {FORMAT_EN_US_SHORT, "-0.0", new BigDecimal("-0.0")}, + {FORMAT_EN_US_SHORT, "0", new BigDecimal("0")}, + {FORMAT_EN_US_SHORT, "499", new BigDecimal("499")}, + {FORMAT_EN_US_SHORT, "-499", new BigDecimal("-499")}, + {FORMAT_EN_US_SHORT, "499.89", new BigDecimal("499.89")}, + {FORMAT_EN_US_SHORT, "-499.89", new BigDecimal("-499.89")}, + {FORMAT_EN_US_SHORT, "1K", new BigDecimal("1000")}, + {FORMAT_EN_US_SHORT, "-1K", new BigDecimal("-1000")}, + {FORMAT_EN_US_SHORT, "3K", new BigDecimal("3000")}, + {FORMAT_EN_US_SHORT, "-3K", new BigDecimal("-3000")}, + {FORMAT_EN_US_SHORT, "17K", new BigDecimal("17000")}, + {FORMAT_EN_US_SHORT, "-17K", new BigDecimal("-17000")}, + {FORMAT_EN_US_SHORT, "12345678901234567890", + new BigDecimal("12345678901234567890")}, + {FORMAT_EN_US_SHORT, "12345679T", new BigDecimal("12345679000000000000")}, + {FORMAT_EN_US_SHORT, "-12345679T", new BigDecimal("-12345679000000000000")}, + {FORMAT_EN_US_SHORT, "599.01K", new BigDecimal("599010.00")}, + {FORMAT_EN_US_SHORT, "-599.01K", new BigDecimal("-599010.00")}, + {FORMAT_EN_US_SHORT, "599444444.90T", new BigDecimal("599444444900000000000.00")}, + {FORMAT_EN_US_SHORT, "-599444444.90T", new BigDecimal("-599444444900000000000.00")}, + {FORMAT_EN_US_SHORT, "123456789012345.5678K", + new BigDecimal("123456789012345567.8000")}, + {FORMAT_EN_US_SHORT, "17.000K", new BigDecimal("17000.000")}, + {FORMAT_EN_US_SHORT, "123.56678K", new BigDecimal("123566.78000")}, + {FORMAT_EN_US_SHORT, "-123.56678K", new BigDecimal("-123566.78000")}, + {FORMAT_EN_LONG, "999", new BigDecimal("999")}, + {FORMAT_EN_LONG, "1 thousand", new BigDecimal("1000")}, + {FORMAT_EN_LONG, "3 thousand", new BigDecimal("3000")}, + {FORMAT_EN_LONG, "12345679 trillion", new BigDecimal("12345679000000000000")}, + {FORMAT_HI_IN_LONG, "999", new BigDecimal("999")}, + {FORMAT_HI_IN_LONG, "-999", new BigDecimal("-999")}, + {FORMAT_HI_IN_LONG, "1 \u0939\u091C\u093C\u093E\u0930", new BigDecimal("1000")}, + {FORMAT_HI_IN_LONG, "-1 \u0939\u091C\u093C\u093E\u0930", new BigDecimal("-1000")}, + {FORMAT_HI_IN_LONG, "3 \u0939\u091C\u093C\u093E\u0930", new BigDecimal("3000")}, + {FORMAT_HI_IN_LONG, "12345679 \u0916\u0930\u092C", new BigDecimal("1234567900000000000")}, + {FORMAT_HI_IN_LONG, "-12345679 \u0916\u0930\u092C", new BigDecimal("-1234567900000000000")}, + {FORMAT_JA_JP_SHORT, "-99", new BigDecimal("-99")}, + {FORMAT_JA_JP_SHORT, "1\u4E07", new BigDecimal("10000")}, + {FORMAT_JA_JP_SHORT, "30\u4E07", new BigDecimal("300000")}, + {FORMAT_JA_JP_SHORT, "-30\u4E07", new BigDecimal("-300000")}, + {FORMAT_JA_JP_SHORT, "12345679\u5146", new BigDecimal("12345679000000000000")}, + {FORMAT_JA_JP_SHORT, "-12345679\u5146", new BigDecimal("-12345679000000000000")}, + {FORMAT_IT_SHORT, "-99", new BigDecimal("-99")}, + {FORMAT_IT_SHORT, "1\u00a0Mln", new BigDecimal("1000000")}, + {FORMAT_IT_SHORT, "30\u00a0Mln", new BigDecimal("30000000")}, + {FORMAT_IT_SHORT, "-30\u00a0Mln", new BigDecimal("-30000000")}, + {FORMAT_IT_SHORT, "12345679\u00a0Bln", new BigDecimal("12345679000000000000")}, + {FORMAT_IT_SHORT, "-12345679\u00a0Bln", new BigDecimal("-12345679000000000000")}, + {FORMAT_SW_LONG, "-0.0", new BigDecimal("-0.0")}, + {FORMAT_SW_LONG, "499", new BigDecimal("499")}, + {FORMAT_SW_LONG, "elfu 1", new BigDecimal("1000")}, + {FORMAT_SW_LONG, "elfu 3", new BigDecimal("3000")}, + {FORMAT_SW_LONG, "elfu 17", new BigDecimal("17000")}, + {FORMAT_SW_LONG, "elfu -3", new BigDecimal("-3000")}, + {FORMAT_SW_LONG, "-499", new BigDecimal("-499")}, + {FORMAT_SW_LONG, "elfu 1", new BigDecimal("1000")}, + {FORMAT_SW_LONG, "elfu 3", new BigDecimal("3000")}, + {FORMAT_SW_LONG, "elfu -3", new BigDecimal("-3000")}, + {FORMAT_SW_LONG, "elfu 17", new BigDecimal("17000")}, + {FORMAT_SW_LONG, "trilioni 12345679", new BigDecimal("12345679000000000000")}, + {FORMAT_SW_LONG, "trilioni -12345679", new BigDecimal("-12345679000000000000")}, + {FORMAT_SW_LONG, "elfu 599.01", new BigDecimal("599010.00")}, + {FORMAT_SW_LONG, "elfu -599.01", new BigDecimal("-599010.00")}, + {FORMAT_SE_SHORT, "999", new BigDecimal("999")}, + {FORMAT_SE_SHORT, "8\u00a0mn", new BigDecimal("8000000")}, + {FORMAT_SE_SHORT, "8\u00a0dt", new BigDecimal("8000")}, + {FORMAT_SE_SHORT, "12345679\u00a0bn", new BigDecimal("12345679000000000000")}, + {FORMAT_SE_SHORT, "12345679,89\u00a0bn", new BigDecimal("12345679890000000000.00")}, + {FORMAT_SE_SHORT, "\u2212999", new BigDecimal("-999")}, + {FORMAT_SE_SHORT, "\u22128\u00a0mn", new BigDecimal("-8000000")}, + {FORMAT_SE_SHORT, "\u22128\u00a0dt", new BigDecimal("-8000")}, + {FORMAT_SE_SHORT, "\u221212345679\u00a0bn", new BigDecimal("-12345679000000000000")}, + {FORMAT_SE_SHORT, "\u221212345679,89\u00a0bn", new BigDecimal("-12345679890000000000.00")},}; + } + + @Test(dataProvider = "parse") + public void testParse(NumberFormat cnf, String parseString, + Number expected) throws ParseException { + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, BigDecimal.class); + } +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestSpecialValues.java 2018-11-28 14:09:18.218276887 +0530 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the formatting and parsing of special values + * @modules jdk.localedata + * @run testng/othervm TestSpecialValues + */ +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestSpecialValues { + + private static final NumberFormat FORMAT = NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + + @DataProvider(name = "formatSpecialValues") + Object[][] formatSpecialValues() { + return new Object[][]{ + // number , formatted ouput + {+0.0, "0"}, + {-0.0, "-0"}, + {Double.MIN_VALUE, "0"}, + {Double.MIN_NORMAL, "0"}, + {Double.NaN, "NaN"}, + {Double.POSITIVE_INFINITY, "\u221E"}, + {Double.NEGATIVE_INFINITY, "-\u221E"}, + {Long.MIN_VALUE, "-9223372T"}, + {Long.MAX_VALUE, "9223372T"},}; + } + + @DataProvider(name = "parseSpecialValues") + Object[][] parseSpecialValues() { + return new Object[][]{ + // parse string, parsed number + {"-0.0", -0.0}, + {"" + Long.MIN_VALUE, Long.MIN_VALUE}, + {"" + Long.MAX_VALUE, Long.MAX_VALUE}, + {"NaN", Double.NaN}, + {"\u221E", Double.POSITIVE_INFINITY}, + {"-\u221E", Double.NEGATIVE_INFINITY},}; + } + + @Test(dataProvider = "formatSpecialValues") + public void testFormatSpecialValues(Object number, String expected) { + CompactFormatAndParseHelper.testFormat(FORMAT, number, expected); + } + + @Test(dataProvider = "parseSpecialValues") + public void testParseSpecialValues(String parseString, Number expected) + throws ParseException { + CompactFormatAndParseHelper.testParse(FORMAT, parseString, expected, null, null); + } +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestUExtensionOverride.java 2018-11-28 14:09:18.990276887 +0530 @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the behaviour of Unicode BCP 47 U Extension with + * compact number format + * @modules jdk.localedata + * @run testng/othervm TestUExtensionOverride + */ +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestUExtensionOverride { + + @DataProvider(name = "compactFormatData") + Object[][] compactFormatData() { + return new Object[][]{ + // locale, number, formatted string + + // -nu + {Locale.forLanguageTag("en-US-u-nu-deva"), 12345, "\u0967\u0968K"}, + {Locale.forLanguageTag("en-US-u-nu-sinh"), 12345, "\u0de7\u0de8K"}, + {Locale.forLanguageTag("en-US-u-nu-zzzz"), 12345, "12K"}, + // -rg + {Locale.forLanguageTag("fr-FR-u-rg-cazzzz"), 1234567, + "1\u00a0234\u00a0567"}, + {Locale.forLanguageTag("fr-FR-u-rg-cazzzz"), 1234567890, + "1\u00a0G"}, + // -nu and -rg + {Locale.forLanguageTag("en-US-u-nu-deva-rg-dezzzz"), 12345, + "\u0967\u0968K"}, + {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-cazzzz"), 1234567890, + "1\u00a0Md"}, + {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-zzzz"), 12345, + "12\u00a0k"}, + {Locale.forLanguageTag("fr-FR-u-rg-cazzzz-nu-deva"), 12345, + "\u0967\u0968\u00a0k"},}; + } + + @DataProvider(name = "compactParseData") + Object[][] compactParseData() { + return new Object[][]{ + // locale, parse string, parsed number + + // -nu + {Locale.forLanguageTag("en-US-u-nu-deva"), + "\u0967\u0968K", 12000L}, + {Locale.forLanguageTag("en-US-u-nu-sinh"), + "\u0de7\u0de8K", 12000L}, + {Locale.forLanguageTag("en-US-u-nu-zzzz"), + "12K", 12000L}, + // -rg + {Locale.forLanguageTag("fr-FR-u-rg-cazzzz"), + "1\u00a0G", 1000000000L}, + // -nu and -rg + {Locale.forLanguageTag("en-US-u-nu-deva-rg-dezzzz"), + "\u0967\u0968K", 12000L}, + {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-cazzzz"), + "1\u00a0Md", 1000000000L}, + {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-zzzz"), + "12\u00a0k", 12000L}, + {Locale.forLanguageTag("fr-FR-u-rg-cazzzz-nu-deva"), + "\u0967\u0968\u00a0k", 12000L},}; + } + + @Test(dataProvider = "compactFormatData") + public void testFormat(Locale locale, double num, + String expected) { + NumberFormat cnf = NumberFormat.getCompactNumberInstance(locale, + NumberFormat.Style.SHORT); + CompactFormatAndParseHelper.testFormat(cnf, num, expected); + } + + @Test(dataProvider = "compactParseData") + public void testParse(Locale locale, String parseString, + Number expected) throws ParseException { + NumberFormat cnf = NumberFormat.getCompactNumberInstance(locale, + NumberFormat.Style.SHORT); + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/TestWithCompatProvider.java 2018-11-28 14:09:19.786276887 +0530 @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @summary Checks the compact number format with COMPAT provider. Since the + * compact number resources are only provided by CLDR, using COMPAT + * as a provider should always use the default patterns added in the + * FormatData.java resource bundle + * @modules jdk.localedata + * @run testng/othervm -Djava.locale.providers=COMPAT TestWithCompatProvider + */ +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class TestWithCompatProvider { + + private static final NumberFormat FORMAT_DZ_SHORT = NumberFormat + .getCompactNumberInstance(new Locale("dz"), NumberFormat.Style.SHORT); + + private static final NumberFormat FORMAT_EN_US_SHORT = NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT); + + @DataProvider(name = "format") + Object[][] compactFormatData() { + return new Object[][]{ + {FORMAT_DZ_SHORT, 1000.09, "1K"}, + {FORMAT_DZ_SHORT, -999.99, "-1K"}, + {FORMAT_DZ_SHORT, -0.0, "-0"}, + {FORMAT_DZ_SHORT, new BigInteger("12345678901234567890"), "12345679T"}, + {FORMAT_DZ_SHORT, new BigDecimal("12345678901234567890.89"), "12345679T"}, + {FORMAT_EN_US_SHORT, -999.99, "-1K"}, + {FORMAT_EN_US_SHORT, 9999, "10K"}, + {FORMAT_EN_US_SHORT, 3000.90, "3K"}, + {FORMAT_EN_US_SHORT, new BigInteger("12345678901234567890"), "12345679T"}, + {FORMAT_EN_US_SHORT, new BigDecimal("12345678901234567890.89"), "12345679T"},}; + } + + @DataProvider(name = "parse") + Object[][] compactParseData() { + return new Object[][]{ + {FORMAT_DZ_SHORT, "1K", 1000L}, + {FORMAT_DZ_SHORT, "-3K", -3000L}, + {FORMAT_DZ_SHORT, "12345700T", 1.23457E19}, + {FORMAT_EN_US_SHORT, "-99", -99L}, + {FORMAT_EN_US_SHORT, "10K", 10000L}, + {FORMAT_EN_US_SHORT, "12345679T", 1.2345679E19},}; + } + + @Test(dataProvider = "format") + public void testFormat(NumberFormat cnf, Object number, + String expected) { + CompactFormatAndParseHelper.testFormat(cnf, number, expected); + } + + @Test(dataProvider = "parse") + public void testParse(NumberFormat cnf, String parseString, + Number expected) throws ParseException { + CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null); + } + +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/serialization/TestDeserializeCNF.java 2018-11-28 14:09:20.618276887 +0530 @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8177552 + * @modules jdk.localedata + * @summary Checks deserialization of compact number format + * @library /java/text/testlib + * @build TestDeserializeCNF HexDumpReader + * @run testng/othervm TestDeserializeCNF + */ + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.math.RoundingMode; +import java.text.CompactNumberFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import static org.testng.Assert.*; + +public class TestDeserializeCNF { + + // This object is serialized in cnf1.ser.txt with HALF_UP + // rounding mode, groupingsize = 3 and parseBigDecimal = true + private static final CompactNumberFormat COMPACT_FORMAT1 = new CompactNumberFormat("#,##0.###", + DecimalFormatSymbols.getInstance(Locale.US), + new String[]{"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"}); + + // This object is serialized in cnf2.ser.txt with min integer digits = 20 + // and min fraction digits = 5 + private static final CompactNumberFormat COMPACT_FORMAT2 = new CompactNumberFormat("#,##0.###", + DecimalFormatSymbols.getInstance(Locale.JAPAN), + new String[]{"", "", "", "0", "0\u4e07", "00\u4e07", "000\u4e07", "0000\u4e07", "0\u5104", "00\u5104", "000\u5104", "0000\u5104", "0\u5146", "00\u5146", "000\u5146"}); + + private static final String FILE_COMPACT_FORMAT1 = "cnf1.ser.txt"; + private static final String FILE_COMPACT_FORMAT2 = "cnf2.ser.txt"; + + @BeforeTest + public void mutateInstances() { + COMPACT_FORMAT1.setRoundingMode(RoundingMode.HALF_UP); + COMPACT_FORMAT1.setGroupingSize(3); + COMPACT_FORMAT1.setParseBigDecimal(true); + + COMPACT_FORMAT2.setMinimumIntegerDigits(20); + COMPACT_FORMAT2.setMinimumFractionDigits(5); + } + + @Test + public void testDeserialization() throws IOException, ClassNotFoundException { + try (InputStream istream1 = HexDumpReader.getStreamFromHexDump(FILE_COMPACT_FORMAT1); + ObjectInputStream ois1 = new ObjectInputStream(istream1); + InputStream istream2 = HexDumpReader.getStreamFromHexDump(FILE_COMPACT_FORMAT2); + ObjectInputStream ois2 = new ObjectInputStream(istream2);) { + + CompactNumberFormat obj1 = (CompactNumberFormat) ois1.readObject(); + assertEquals(obj1, COMPACT_FORMAT1, "Deserialized instance is not" + + " equal to the instance serialized in " + FILE_COMPACT_FORMAT1); + + CompactNumberFormat obj2 = (CompactNumberFormat) ois2.readObject(); + assertEquals(obj2, COMPACT_FORMAT2, "Deserialized instance is not" + + " equal to the instance serialized in " + FILE_COMPACT_FORMAT2); + } + } + + // The objects are serialized using the serialize() method, the hex + // dump printed is copied to respective object files +// void serialize(CompactNumberFormat cnf) { +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { +// oos.writeObject(cnf); +// } catch (IOException ioe) { +// throw new RuntimeException(ioe); +// } +// byte[] ser = baos.toByteArray(); +// for (byte b : ser) { +// System.out.print("" + String.format("%02x", b)); +// } +// } + +} + --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/serialization/TestSerialization.java 2018-11-28 14:09:21.414276887 +0530 @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8177552 + * @modules jdk.localedata + * @summary Checks the serialization feature of CompactNumberFormat + * @run testng/othervm TestSerialization + */ + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.RoundingMode; +import java.text.CompactNumberFormat; +import java.text.NumberFormat; +import java.util.Locale; +import static org.testng.Assert.*; + +public class TestSerialization { + + private static final NumberFormat FORMAT_HI = NumberFormat.getCompactNumberInstance( + new Locale("hi"), NumberFormat.Style.SHORT); + private static final CompactNumberFormat FORMAT_EN_US = (CompactNumberFormat) NumberFormat + .getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG); + private static final NumberFormat FORMAT_JA_JP = NumberFormat.getCompactNumberInstance( + Locale.JAPAN, NumberFormat.Style.SHORT); + private static final NumberFormat FORMAT_FR_FR = NumberFormat.getCompactNumberInstance( + Locale.FRANCE, NumberFormat.Style.LONG); + private static final NumberFormat FORMAT_DE_DE = NumberFormat.getCompactNumberInstance( + Locale.GERMANY, NumberFormat.Style.SHORT); + private static final NumberFormat FORMAT_KO_KR = NumberFormat.getCompactNumberInstance( + Locale.KOREA, NumberFormat.Style.SHORT); + + @BeforeTest + public void mutateInstances() { + FORMAT_HI.setMinimumFractionDigits(2); + FORMAT_HI.setMinimumIntegerDigits(5); + + FORMAT_EN_US.setRoundingMode(RoundingMode.HALF_UP); + FORMAT_EN_US.setGroupingSize(3); + FORMAT_EN_US.setParseBigDecimal(true); + + FORMAT_JA_JP.setMaximumFractionDigits(30); + FORMAT_JA_JP.setMaximumIntegerDigits(30); + + FORMAT_FR_FR.setParseIntegerOnly(true); + FORMAT_FR_FR.setGroupingUsed(true); + + // Setting minimum integer digits beyond the allowed range + FORMAT_DE_DE.setMinimumIntegerDigits(320); + + // Setting minimum fraction digits beyond the allowed range + FORMAT_KO_KR.setMinimumFractionDigits(350); + } + + @Test + public void testSerialization() throws IOException, ClassNotFoundException { + // Serialize + serialize("cdf.ser", FORMAT_HI, FORMAT_EN_US, FORMAT_JA_JP, FORMAT_FR_FR, FORMAT_DE_DE, FORMAT_KO_KR); + // Deserialize + deserialize("cdf.ser", FORMAT_HI, FORMAT_EN_US, FORMAT_JA_JP, FORMAT_FR_FR, FORMAT_DE_DE, FORMAT_KO_KR); + } + + private void serialize(String fileName, NumberFormat... formats) + throws IOException { + try (ObjectOutputStream os = new ObjectOutputStream( + new FileOutputStream(fileName))) { + for (NumberFormat fmt : formats) { + os.writeObject(fmt); + } + } + } + + private static void deserialize(String fileName, NumberFormat... formats) + throws IOException, ClassNotFoundException { + try (ObjectInputStream os = new ObjectInputStream( + new FileInputStream(fileName))) { + for (NumberFormat fmt : formats) { + NumberFormat obj = (NumberFormat) os.readObject(); + assertEquals(fmt, obj, "Serialized and deserialized" + + " objects do not match"); + + long number = 123456789789L; + String expected = fmt.format(number); + String actual = obj.format(number); + assertEquals(actual, expected, "Serialized and deserialized" + + " objects are expected to return same formatted" + + " output for number: " + number); + } + } + } +} --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/serialization/cnf1.ser.txt 2018-11-28 14:09:22.158276887 +0530 @@ -0,0 +1,64 @@ +# +# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# Hex dump of a serialized CompactNumberFormat for TestDeserializeCNF. + +aced00057372001d6a6176612e746578742e436f6d706163744e756d626572466f726d +617462ed0c37b8207cf602000642000c67726f7570696e6753697a655a000f70617273 +65426967446563696d616c5b000f636f6d706163745061747465726e737400135b4c6a +6176612f6c616e672f537472696e673b4c000e646563696d616c5061747465726e7400 +124c6a6176612f6c616e672f537472696e673b4c000c726f756e64696e674d6f646574 +00184c6a6176612f6d6174682f526f756e64696e674d6f64653b4c000773796d626f6c +737400204c6a6176612f746578742f446563696d616c466f726d617453796d626f6c73 +3b787200166a6176612e746578742e4e756d626572466f726d6174dff6b3bf137d07e8 +03000b5a000c67726f7570696e67557365644200116d61784672616374696f6e446967 +6974734200106d6178496e74656765724469676974734900156d6178696d756d467261 +6374696f6e4469676974734900146d6178696d756d496e746567657244696769747342 +00116d696e4672616374696f6e4469676974734200106d696e496e7465676572446967 +6974734900156d696e696d756d4672616374696f6e4469676974734900146d696e696d +756d496e74656765724469676974735a00107061727365496e74656765724f6e6c7949 +001573657269616c56657273696f6e4f6e53747265616d787200106a6176612e746578 +742e466f726d6174fbd8bc12e90f1843020000787000007f0000000000000135000100 +000000000000010000000001780301757200135b4c6a6176612e6c616e672e53747269 +6e673badd256e7e91d7b4702000078700000000f74000071007e000a71007e000a7400 +02304b74000330304b7400043030304b740002304d74000330304d7400043030304d74 +0002304274000330304274000430303042740002305474000330305474000430303054 +740009232c2323302e2323237e7200166a6176612e6d6174682e526f756e64696e674d +6f646500000000000000001200007872000e6a6176612e6c616e672e456e756d000000 +0000000000120000787074000748414c465f55507372001e6a6176612e746578742e44 +6563696d616c466f726d617453796d626f6c73501d17990868939c0200114300106465 +63696d616c536570617261746f72430005646967697443000b6578706f6e656e746961 +6c43001167726f7570696e67536570617261746f724300096d696e75735369676e4300 +116d6f6e6574617279536570617261746f724300107061747465726e53657061726174 +6f724300077065724d696c6c43000770657263656e7449001573657269616c56657273 +696f6e4f6e53747265616d4300097a65726f44696769744c00034e614e71007e00024c +000e63757272656e637953796d626f6c71007e00024c00146578706f6e656e7469616c +536570617261746f7271007e00024c0008696e66696e69747971007e00024c0012696e +746c43757272656e637953796d626f6c71007e00024c00066c6f63616c657400124c6a +6176612f7574696c2f4c6f63616c653b7870002e00230045002c002d002e003b203000 +250000000300307400034e614e7074000145740003e2889e70737200106a6176612e75 +74696c2e4c6f63616c657ef811609c30f9ec03000649000868617368636f64654c0007 +636f756e74727971007e00024c000a657874656e73696f6e7371007e00024c00086c61 +6e677561676571007e00024c000673637269707471007e00024c000776617269616e74 +71007e00027870ffffffff740002555371007e000a740002656e71007e000a71007e00 +0a78 --- /dev/null 2018-11-28 11:17:03.220000000 +0530 +++ new/test/jdk/java/text/Format/CompactNumberFormat/serialization/cnf2.ser.txt 2018-11-28 14:09:22.902276887 +0530 @@ -0,0 +1,64 @@ +# +# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# Hex dump of a serialized CompactNumberFormat for TestDeserializeCNF. + +aced00057372001d6a6176612e746578742e436f6d706163744e756d626572466f726d +617462ed0c37b8207cf602000642000c67726f7570696e6753697a655a000f70617273 +65426967446563696d616c5b000f636f6d706163745061747465726e737400135b4c6a +6176612f6c616e672f537472696e673b4c000e646563696d616c5061747465726e7400 +124c6a6176612f6c616e672f537472696e673b4c000c726f756e64696e674d6f646574 +00184c6a6176612f6d6174682f526f756e64696e674d6f64653b4c000773796d626f6c +737400204c6a6176612f746578742f446563696d616c466f726d617453796d626f6c73 +3b787200166a6176612e746578742e4e756d626572466f726d6174dff6b3bf137d07e8 +03000b5a000c67726f7570696e67557365644200116d61784672616374696f6e446967 +6974734200106d6178496e74656765724469676974734900156d6178696d756d467261 +6374696f6e4469676974734900146d6178696d756d496e746567657244696769747342 +00116d696e4672616374696f6e4469676974734200106d696e496e7465676572446967 +6974734900156d696e696d756d4672616374696f6e4469676974734900146d696e696d +756d496e74656765724469676974735a00107061727365496e74656765724f6e6c7949 +001573657269616c56657273696f6e4f6e53747265616d787200106a6176612e746578 +742e466f726d6174fbd8bc12e90f1843020000787000057f0000000500000135051400 +000005000000140000000001780000757200135b4c6a6176612e6c616e672e53747269 +6e673badd256e7e91d7b4702000078700000000f74000071007e000a71007e000a7400 +013074000430e4b8877400053030e4b887740006303030e4b88774000730303030e4b8 +8774000430e584847400053030e58484740006303030e5848474000730303030e58484 +74000430e585867400053030e58586740006303030e58586740009232c2323302e2323 +237e7200166a6176612e6d6174682e526f756e64696e674d6f64650000000000000000 +1200007872000e6a6176612e6c616e672e456e756d0000000000000000120000787074 +000948414c465f4556454e7372001e6a6176612e746578742e446563696d616c466f72 +6d617453796d626f6c73501d17990868939c020011430010646563696d616c53657061 +7261746f72430005646967697443000b6578706f6e656e7469616c43001167726f7570 +696e67536570617261746f724300096d696e75735369676e4300116d6f6e6574617279 +536570617261746f724300107061747465726e536570617261746f724300077065724d +696c6c43000770657263656e7449001573657269616c56657273696f6e4f6e53747265 +616d4300097a65726f44696769744c00034e614e71007e00024c000e63757272656e63 +7953796d626f6c71007e00024c00146578706f6e656e7469616c536570617261746f72 +71007e00024c0008696e66696e69747971007e00024c0012696e746c43757272656e63 +7953796d626f6c71007e00024c00066c6f63616c657400124c6a6176612f7574696c2f +4c6f63616c653b7870002e00230045002c002d002e003b203000250000000300307400 +034e614e7074000145740003e2889e70737200106a6176612e7574696c2e4c6f63616c +657ef811609c30f9ec03000649000868617368636f64654c0007636f756e7472797100 +7e00024c000a657874656e73696f6e7371007e00024c00086c616e677561676571007e +00024c000673637269707471007e00024c000776617269616e7471007e00027870ffff +ffff7400024a5071007e000a7400026a6171007e000a71007e000a78