--- /dev/null 2018-10-22 22:34:38.096000000 +0530 +++ new/src/java.base/share/classes/java/text/CompactNumberFormat.java 2018-11-16 17:01:16.956934364 +0530 @@ -0,0 +1,2268 @@ +/* + * 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); + *
+ * 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"}. + * + *
+ * 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: + *
+ * + * 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"}. + * + *+ * 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 + *
+ * 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'.'. + * + *
+ * 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(10, patternIndex);
+ } else {
+ matchedValue = BigInteger.valueOf(10).pow(patternIndex);
+ }
+ Number divisor = matchedValue;
+ if (count != 0) {
+ if (matchedValue instanceof BigInteger) {
+ BigInteger bigValue = (BigInteger) matchedValue;
+ if (bigValue.compareTo(BigInteger.valueOf((long) Math.pow(10, 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(10, count)));
+ } else {
+ long longValue = (long) matchedValue;
+ if (longValue < (long) Math.pow(10, count)) {
+ throw new IllegalArgumentException("Invalid Pattern"
+ + " [" + compactPatterns[patternIndex]
+ + "]: min integer digits specified exceeds the limit"
+ + " for the index " + patternIndex);
+ }
+ divisor = longValue / (long) Math.pow(10, 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 {@code 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
+ * 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}.
+ *
+ * Callers may use the {@code Number} methods {@code doubleValue},
+ * {@code longValue}, etc., to obtain the type they want.
+ *
+ *
+ * {@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.
+ *
+ * @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];
+
+ int numPosition = subparseNumber(text, position, digitList, status);
+
+ Number multiplier = 1L;
+ if (numPosition == -1) {
+ // unable to parse the number successfully
+ pos.index = oldStart;
+ pos.errorIndex = oldStart;
+ return null;
+ } else {
+ // 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;
+ }
+ }
+
+ /**
+ * 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 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
+ */
+ private int subparseNumber(String text, int position, DigitList digits,
+ boolean status[]) {
+
+ // process digits or Inf, find decimal position
+ status[STATUS_INFINITE] = false;
+ if (text.regionMatches(position, symbols.getInfinity(), 0,
+ symbols.getInfinity().length())) {
+ position += symbols.getInfinity().length();
+ status[STATUS_INFINITE] = true;
+ } else {
+ // We now have a string of digits, possibly with grouping symbols,
+ // and decimal points. We want to process these into a DigitList.
+ // We don't want to put a bunch of leading zeros into the DigitList
+ // though, so we keep track of the location of the decimal point,
+ // put only significant digits into the DigitList, and adjust the
+ // exponent as needed.
+
+ digits.decimalAt = digits.count = 0;
+ char zero = symbols.getZeroDigit();
+ char decimal = symbols.getDecimalSeparator();
+ char grouping = symbols.getGroupingSeparator();
+ boolean sawDecimal = false;
+ boolean sawDigit = false;
+
+ // We have to track digitCount ourselves, because digits.count will
+ // pin when the maximum allowable digits is reached.
+ int digitCount = 0;
+
+ int backup = -1;
+ char ch = '\0';
+ for (; position < text.length(); ++position) {
+ ch = text.charAt(position);
+
+ /* We recognize all digit ranges, not only the Latin digit range
+ * '0'..'9'. We do so by using the Character.digit() method,
+ * which converts a valid Unicode digit to the range 0..9.
+ *
+ * The character 'ch' may be a digit. If so, place its value
+ * from 0 to 9 in 'digit'. First try using the locale digit,
+ * which may or MAY NOT be a standard Unicode digit range. If
+ * this fails, try using the standard Unicode digit ranges by
+ * calling Character.digit(). If this also fails, digit will
+ * have a value outside the range 0..9.
+ */
+ int digit = ch - zero;
+ if (digit < 0 || digit > 9) {
+ digit = Character.digit(ch, 10);
+ }
+
+ if (digit == 0) {
+ // Cancel out backup setting (see grouping handler below)
+ backup = -1; // Do this BEFORE continue statement below!!!
+ sawDigit = true;
+
+ // Handle leading zeros
+ if (digits.count == 0) {
+ // Ignore leading zeros in integer part of number.
+ if (!sawDecimal) {
+ continue;
+ }
+
+ // If we have seen the decimal, but no significant
+ // digits yet, then we account for leading zeros by
+ // decrementing the digits.decimalAt into negative
+ // values.
+ --digits.decimalAt;
+ } else {
+ ++digitCount;
+ digits.append((char) (digit + '0'));
+ }
+ } else if (digit > 0 && digit <= 9) {
+ sawDigit = true;
+ ++digitCount;
+ digits.append((char) (digit + '0'));
+
+ // Cancel out backup setting (see grouping handler below)
+ backup = -1;
+ } else if (ch == decimal) {
+ // If we're only parsing integers, or if we ALREADY saw the
+ // decimal, then don't parse this one.
+ if (isParseIntegerOnly() || sawDecimal) {
+ break;
+ }
+ digits.decimalAt = digitCount; // Not digits.count!
+ sawDecimal = true;
+ } else if (ch == grouping && isGroupingUsed()) {
+ if (sawDecimal) {
+ break;
+ }
+ // Ignore grouping characters, if we are using them, but
+ // require that they be followed by a digit. Otherwise
+ // we backup and reprocess them.
+ backup = position;
+ } else {
+ break;
+ }
+ }
+
+ if (backup != -1) {
+ position = backup;
+ }
+
+ // If there was no decimal point we have an integer
+ if (!sawDecimal) {
+ digits.decimalAt = digitCount; // Not digits.count!
+ }
+
+ // 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() && ch == decimal) {
+ position++; // pass decimal character
+ for (; position < text.length(); ++position) {
+ ch = text.charAt(position);
+ int digit = ch - zero;
+ if (digit < 0 || digit > 9) {
+ digit = Character.digit(ch, 10);
+ // parse all digit characters
+ if (!(digit >= 0 && digit <= 9)) {
+ break;
+ }
+ }
+ }
+ }
+
+ // If none of the text string was recognized.
+ if (!sawDigit && digitCount == 0) {
+ return -1;
+ }
+ }
+ return position;
+ }
+
+ /**
+ * 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,
+ *
+ *
+ *
+ *
+ *
+ * @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());
+ }
+
+ }
+
+ /**
+ * Returns the maximum number of digits allowed in the integer portion of a
+ * number.
+ *
+ * @return the maximum number of integer digits
+ * @see #setMaximumIntegerDigits(int)
+ */
+ @Override
+ public int getMaximumIntegerDigits() {
+ return super.getMaximumIntegerDigits();
+ }
+
+ /**
+ * 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());
+ }
+ }
+
+ /**
+ * Returns the minimum number of digits allowed in the integer portion of a
+ * number.
+ *
+ * @return the minimum number of integer digits
+ * @see #setMinimumIntegerDigits(int)
+ */
+ @Override
+ public int getMinimumIntegerDigits() {
+ return super.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());
+ }
+ }
+
+ /**
+ * Returns the minimum number of digits allowed in the fraction portion of a
+ * number.
+ *
+ * @return the minimum number of fraction digits
+ * @see #setMinimumFractionDigits(int)
+ */
+ @Override
+ public int getMinimumFractionDigits() {
+ return super.getMinimumFractionDigits();
+ }
+
+ /**
+ * 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());
+ }
+ }
+
+ /**
+ * Returns the maximum number of digits allowed in the fraction portion of a
+ * number.
+ *
+ * @return the maximum number of fraction digits
+ * @see #setMaximumFractionDigits(int)
+ */
+ @Override
+ public int getMaximumFractionDigits() {
+ return super.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;
+ }
+
+}
+