| /* |
| * Copyright (c) 2018, 2019, 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; |
| |
| |
| /** |
| * <p> |
| * {@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 |
| * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats"> |
| * Compact Number Formats</a>. A compact number formatting refers |
| * to the representation of a number in a shorter form, based on the patterns |
| * provided for a given locale. |
| * |
| * <p> |
| * For example: |
| * <br>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 |
| * <a href = "#compact_number_style" >style</a> used. |
| * <br>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 <a href = "#compact_number_style" >style</a> used. |
| * |
| * <p> |
| * 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)}. |
| * |
| * <blockquote><pre> |
| * NumberFormat fmt = NumberFormat.getCompactNumberInstance( |
| * new Locale("hi", "IN"), NumberFormat.Style.SHORT); |
| * String result = fmt.format(1000); |
| * </pre></blockquote> |
| * |
| * <h3><a id="compact_number_style">Style</a></h3> |
| * <p> |
| * 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"}. |
| * |
| * <h3><a id="compact_number_patterns">Compact Number Patterns</a></h3> |
| * <p> |
| * 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}<sup>{@code 0}</sup> to {@code 10}<sup>{@code 14}</sup>. |
| * There can be any number of patterns and they are |
| * strictly index based starting from the range {@code 10}<sup>{@code 0}</sup>. |
| * 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}<sup>{@code 0}</sup>-{@code 10}<sup>{@code 2}</sup> 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. |
| * <p> |
| * A compact pattern has the following syntax: |
| * <blockquote><pre> |
| * <i>Pattern:</i> |
| * <i>PositivePattern</i> |
| * <i>PositivePattern</i> <i>[; NegativePattern]<sub>optional</sub></i> |
| * <i>PositivePattern:</i> |
| * <i>Prefix<sub>optional</sub></i> <i>MinimumInteger</i> <i>Suffix<sub>optional</sub></i> |
| * <i>NegativePattern:</i> |
| * <i>Prefix<sub>optional</sub></i> <i>MinimumInteger</i> <i>Suffix<sub>optional</sub></i> |
| * <i>Prefix:</i> |
| * Any Unicode characters except \uFFFE, \uFFFF, and |
| * <a href = "DecimalFormat.html#special_pattern_character">special characters</a> |
| * <i>Suffix:</i> |
| * Any Unicode characters except \uFFFE, \uFFFF, and |
| * <a href = "DecimalFormat.html#special_pattern_character">special characters</a> |
| * <i>MinimumInteger:</i> |
| * 0 |
| * 0 <i>MinimumInteger</i> |
| * </pre></blockquote> |
| * |
| * 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"}. |
| * |
| * <p> |
| * Many characters in a compact pattern are taken literally, they are matched |
| * during parsing and output unchanged during formatting. |
| * <a href = "DecimalFormat.html#special_pattern_character">Special characters</a>, |
| * 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'.'. |
| * |
| * <h3>Formatting</h3> |
| * 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. |
| * |
| * <h3>Parsing</h3> |
| * 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. |
| * |
| * <h3>Rounding</h3> |
| * {@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}<sup>{@code 0}</sup>-{@code 10}<sup>{@code 14}</sup>, |
| * 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<String> positivePrefixPatterns; |
| |
| /** |
| * List of negative prefix patterns of this formatter's |
| * compact number patterns. |
| */ |
| private transient List<String> negativePrefixPatterns; |
| |
| /** |
| * List of positive suffix patterns of this formatter's |
| * compact number patterns. |
| */ |
| private transient List<String> positiveSuffixPatterns; |
| |
| /** |
| * List of negative suffix patterns of this formatter's |
| * compact number patterns. |
| */ |
| private transient List<String> 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<Number> 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 |
| * <a href = "CompactNumberFormat.html#compact_number_patterns"> |
| * compact number patterns</a> |
| * @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 == null) { |
| throw new IllegalArgumentException("Cannot format null as a number"); |
| } |
| |
| 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 |
| // 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 |
| 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.toString()), getRoundingMode()); |
| decimalFormat.setDigitList(number, isNegative, getMaximumFractionDigits()); |
| decimalFormat.subformatNumber(result, delegate, isNegative, |
| false, getMaximumIntegerDigits(), getMinimumIntegerDigits(), |
| getMaximumFractionDigits(), getMinimumFractionDigits()); |
| appendSuffix(result, suffix, delegate); |
| } else { |
| number = isNegative ? number.negate() : number; |
| defaultDecimalFormat.format(number, result, delegate); |
| } |
| } else { |
| 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()), getRoundingMode()); |
| 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 suffix 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<FieldPosition> positions) { |
| if (!string.isEmpty()) { |
| int start = result.length(); |
| result.append(string); |
| for (int counter = 0; counter < positions.size(); counter++) { |
| FieldPosition fp = positions.get(counter); |
| Format.Field attribute = fp.getFieldAttribute(); |
| delegate.formatted(attribute, attribute, |
| start + fp.getBeginIndex(), |
| start + fp.getEndIndex(), result); |
| } |
| } |
| } |
| |
| /** |
| * 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) { |
| // Return if no quoted character exists |
| if (pattern.indexOf(QUOTE) < 0) { |
| return 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<FieldPosition> getFieldPositions(String pattern, Field field) { |
| List<FieldPosition> 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) { |
| // Input number is greater than current type; try matching with |
| // the next |
| matchedIndex = index; |
| currentValue = currentValue.multiply(BigInteger.valueOf(RANGE_MULTIPLIER)); |
| continue; |
| } |
| if (number.compareTo(currentValue) < 0) { |
| // Current type is greater than the input number; |
| // take the previous pattern |
| break; |
| } else { |
| // Equal |
| matchedIndex = index; |
| 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. |
| * <p> |
| * 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) { |
| |
| if (pattern == null) { |
| throw new IllegalArgumentException("A null compact pattern" + |
| " encountered at index: " + 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 ((pos + 1) < pattern.length() |
| && pattern.charAt(pos + 1) == QUOTE) { |
| ++pos; |
| affix.append("''"); // o''clock |
| } else { |
| inQuote = true; // 'do' |
| } |
| continue; |
| 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, |
| // for example, 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 (inQuote) { |
| throw new IllegalArgumentException("Invalid single quote" |
| + " in pattern \"" + pattern + "\""); |
| } |
| |
| 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.isEmpty() && zeros.isEmpty()) { |
| throw new IllegalArgumentException("Invalid pattern" |
| + " [" + pattern + "]: all patterns must include digit" |
| + " placement 0s"); |
| } |
| |
| // Only if positive affix exists; else put empty strings |
| if (!positivePrefix.isEmpty() || !positiveSuffix.isEmpty()) { |
| positivePrefixPatterns.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<String> positivePrefixes; |
| private transient List<String> negativePrefixes; |
| private transient List<String> positiveSuffixes; |
| private transient List<String> 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}. |
| * <p> |
| * 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. |
| * <p> |
| * 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}. |
| * <ul> |
| * <li>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. |
| * <p> |
| * Callers may use the {@code Number} methods {@code doubleValue}, |
| * {@code longValue}, etc., to obtain the type they want. |
| * |
| * <li>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. |
| * </ul> |
| * <p> |
| * {@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. |
| * <p> |
| * {@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 = ""; |
| String defaultPosPrefix = defaultDecimalFormat.getPositivePrefix(); |
| String defaultNegPrefix = defaultDecimalFormat.getNegativePrefix(); |
| // Prefix matching |
| for (int compactIndex = 0; compactIndex < compactPatterns.length; compactIndex++) { |
| String positivePrefix = positivePrefixes.get(compactIndex); |
| String negativePrefix = negativePrefixes.get(compactIndex); |
| |
| // 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" |
| boolean match = matchAffix(text, position, positivePrefix, |
| defaultPosPrefix, matchedPosPrefix); |
| if (match) { |
| matchedPosIndex = compactIndex; |
| matchedPosPrefix = positivePrefix; |
| gotPositive = true; |
| } |
| |
| match = matchAffix(text, position, negativePrefix, |
| defaultNegPrefix, matchedNegPrefix); |
| if (match) { |
| 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) { |
| if (text.regionMatches(pos.index, defaultPosPrefix, 0, |
| defaultPosPrefix.length())) { |
| // Matches the default positive prefix |
| matchedPosPrefix = defaultPosPrefix; |
| gotPositive = true; |
| } |
| if (text.regionMatches(pos.index, defaultNegPrefix, 0, |
| defaultNegPrefix.length())) { |
| // Matches the default negative prefix |
| matchedNegPrefix = defaultNegPrefix; |
| 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 |
| position = decimalFormat.subparseNumber(text, position, |
| digitList, false, false, status); |
| |
| if (position == -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(position) == symbols.getDecimalSeparator()) { |
| position++; // Pass decimal character |
| for (; position < text.length(); ++position) { |
| char ch = text.charAt(position); |
| 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 = position; |
| Number multiplier = computeParseMultiplier(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; |
| } |
| } |
| |
| if (isParseBigDecimal()) { |
| BigDecimal bigDecimalResult = digitList.getBigDecimal(); |
| |
| if (cnfMultiplier.longValue() != 1) { |
| bigDecimalResult = bigDecimalResult |
| .multiply(new BigDecimal(cnfMultiplier.toString())); |
| } |
| if (!status[STATUS_POSITIVE]) { |
| bigDecimalResult = bigDecimalResult.negate(); |
| } |
| return bigDecimalResult; |
| } else { |
| Number cnfResult; |
| 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; |
| } |
| } |
| |
| /** |
| * Attempts to match the given {@code affix} in the |
| * specified {@code text}. |
| */ |
| private boolean matchAffix(String text, int position, String affix, |
| String defaultAffix, String matchedAffix) { |
| |
| // Check with the compact affixes which are non empty and |
| // do not match with default affix |
| if (!affix.isEmpty() && !affix.equals(defaultAffix)) { |
| // Look ahead only for the longer match than the previous match |
| if (matchedAffix.length() < affix.length()) { |
| if (text.regionMatches(position, affix, 0, affix.length())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Attempts to match given {@code prefix} and {@code suffix} in |
| * the specified {@code text}. |
| */ |
| private boolean matchPrefixAndSuffix(String text, int position, String prefix, |
| String matchedPrefix, String defaultPrefix, String suffix, |
| String matchedSuffix, String defaultSuffix) { |
| |
| // 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 |
| |
| if (prefix.equals(matchedPrefix) |
| || matchedPrefix.equals(defaultPrefix)) { |
| return matchAffix(text, position, suffix, defaultSuffix, matchedSuffix); |
| } |
| return false; |
| } |
| |
| /** |
| * Computes multiplier by matching the given {@code matchedPrefix} |
| * and suffix in the specified {@code text} from the lists of |
| * prefixes and 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 computeParseMultiplier(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); |
| |
| // 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" |
| boolean match = matchPrefixAndSuffix(text, position, positivePrefix, matchedPrefix, |
| defaultDecimalFormat.getPositivePrefix(), positiveSuffix, |
| matchedPosSuffix, defaultDecimalFormat.getPositiveSuffix()); |
| if (match) { |
| matchedPosIndex = compactIndex; |
| matchedPosSuffix = positiveSuffix; |
| gotPos = true; |
| } |
| |
| match = matchPrefixAndSuffix(text, position, negativePrefix, matchedPrefix, |
| defaultDecimalFormat.getNegativePrefix(), negativeSuffix, |
| matchedNegSuffix, defaultDecimalFormat.getNegativeSuffix()); |
| if (match) { |
| 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; |
| // 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 { |
| 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, |
| * <ul> |
| * <li> If any of the {@code decimalPattern}, {@code compactPatterns}, |
| * {@code symbols} or {@code roundingMode} is {@code null}. |
| * <li> 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. |
| * <li> 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. |
| * <li> If any of the minimum/maximum integer/fraction digit count is |
| * negative. This check is performed by superclass's readObject. |
| * <li> 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. |
| * <li> If the grouping size is negative or larger than 127. |
| * </ul> |
| * |
| * @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. |
| // For example, 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. For example, 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. For example, 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. |
| // For example, 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; |
| } |
| |
| } |
| |