--- a/make/jdk/src/classes/build/tools/cldrconverter/AbstractLDMLHandler.java Thu Dec 06 11:54:39 2018 +0530
+++ b/make/jdk/src/classes/build/tools/cldrconverter/AbstractLDMLHandler.java Thu Dec 06 12:39:28 2018 +0530
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -151,6 +151,19 @@
}
}
+ void pushStringListEntry(String qName, Attributes attributes, String key) {
+ if (!pushIfIgnored(qName, attributes)) {
+ currentContainer = new StringListEntry(qName, currentContainer, key);
+ }
+ }
+
+ void pushStringListElement(String qName, Attributes attributes, int index) {
+ if (!pushIfIgnored(qName, attributes)) {
+ currentContainer = new StringListElement(qName, currentContainer, index);
+ }
+ }
+
+
private boolean pushIfIgnored(String qName, Attributes attributes) {
if (isIgnored(attributes) || currentContainer instanceof IgnoredContainer) {
pushIgnoredContainer(qName);
--- a/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java Thu Dec 06 11:54:39 2018 +0530
+++ b/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java Thu Dec 06 12:39:28 2018 +0530
@@ -53,6 +53,10 @@
"NumberPatterns/percent"
};
+ private final static String[] COMPACT_NUMBER_PATTERN_KEYS = {
+ "short.CompactNumberPatterns",
+ "long.CompactNumberPatterns"};
+
private final static String[] NUMBER_ELEMENT_KEYS = {
"NumberElements/decimal",
"NumberElements/group",
@@ -228,6 +232,16 @@
}
}
+ for (String k : COMPACT_NUMBER_PATTERN_KEYS) {
+ List<String> patterns = (List<String>) myMap.remove(k);
+ if (patterns != null) {
+ // Replace any null entry with empty strings.
+ String[] arrPatterns = patterns.stream()
+ .map(s -> s == null ? "" : s).toArray(String[]::new);
+ myMap.put(k, arrPatterns);
+ }
+ }
+
// if myMap has any of NUMBER_ELEMENT_KEYS, create a complete NumberElements.
String defaultScript = (String) myMap.get("DefaultNumberingSystem");
@SuppressWarnings("unchecked")
--- a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java Thu Dec 06 11:54:39 2018 +0530
+++ b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java Thu Dec 06 12:39:28 2018 +0530
@@ -888,6 +888,8 @@
copyIfPresent(map, "NumberElements", formatData);
}
copyIfPresent(map, "NumberPatterns", formatData);
+ copyIfPresent(map, "short.CompactNumberPatterns", formatData);
+ copyIfPresent(map, "long.CompactNumberPatterns", formatData);
// put extra number elements for available scripts into formatData, if it is "root"
if (id.equals("root")) {
--- a/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java Thu Dec 06 11:54:39 2018 +0530
+++ b/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java Thu Dec 06 12:39:28 2018 +0530
@@ -52,6 +52,8 @@
private final String id;
private String currentContext = ""; // "format"/"stand-alone"
private String currentWidth = ""; // "wide"/"narrow"/"abbreviated"
+ private String currentStyle = ""; // short, long for decimalFormat
+ private String compactCount = ""; // one or other for decimalFormat
LDMLParseHandler(String id) {
this.id = id;
@@ -503,13 +505,85 @@
// Number format information
//
case "decimalFormatLength":
- if (attributes.getValue("type") == null) {
- // skipping type="short" data
- // for FormatData
- // copy string for later assembly into NumberPatterns
+ String type = attributes.getValue("type");
+ if (null == type) {
+ // format data for decimal number format
pushStringEntry(qName, attributes, "NumberPatterns/decimal");
+ currentStyle = type;
} else {
- pushIgnoredContainer(qName);
+ switch (type) {
+ case "short":
+ case "long":
+ // considering "short" and long for
+ // compact number formatting patterns
+ pushKeyContainer(qName, attributes, type);
+ currentStyle = type;
+ break;
+ default:
+ pushIgnoredContainer(qName);
+ break;
+ }
+ }
+ break;
+ case "decimalFormat":
+ if(currentStyle == null) {
+ pushContainer(qName, attributes);
+ } else {
+ switch (currentStyle) {
+ case "short":
+ pushStringListEntry(qName, attributes,
+ currentStyle+".CompactNumberPatterns");
+ break;
+ case "long":
+ pushStringListEntry(qName, attributes,
+ currentStyle+".CompactNumberPatterns");
+ break;
+ default:
+ pushIgnoredContainer(qName);
+ break;
+ }
+ }
+ break;
+ case "pattern":
+ String containerName = currentContainer.getqName();
+ if (containerName.equals("decimalFormat")) {
+ if (currentStyle == null) {
+ pushContainer(qName, attributes);
+ } else {
+ // The compact number patterns parsing assumes that the order
+ // of patterns are always in the increasing order of their
+ // type attribute i.e. type = 1000...
+ // Between the inflectional forms for a type (e.g.
+ // count = "one" and count = "other" for type = 1000), it is
+ // assumed that the count = "one" always appears before
+ // count = "other"
+ switch (currentStyle) {
+ case "short":
+ case "long":
+ String count = attributes.getValue("count");
+ // first pattern of count = "one" or count = "other"
+ if ((count.equals("one") || count.equals("other"))
+ && compactCount.equals("")) {
+ compactCount = count;
+ pushStringListElement(qName, attributes,
+ (int) Math.log10(Double.parseDouble(attributes.getValue("type"))));
+ } else if ((count.equals("one") || count.equals("other"))
+ && compactCount.equals(count)) {
+ // extract patterns with similar "count"
+ // attribute value
+ pushStringListElement(qName, attributes,
+ (int) Math.log10(Double.parseDouble(attributes.getValue("type"))));
+ } else {
+ pushIgnoredContainer(qName);
+ }
+ break;
+ default:
+ pushIgnoredContainer(qName);
+ break;
+ }
+ }
+ } else {
+ pushContainer(qName, attributes);
}
break;
case "currencyFormatLength":
@@ -676,10 +750,9 @@
// "alias" for root
case "alias":
{
- if (id.equals("root") &&
- !isIgnored(attributes) &&
- currentCalendarType != null &&
- !currentCalendarType.lname().startsWith("islamic-")) { // ignore Islamic variants
+ if (id.equals("root") && !isIgnored(attributes)
+ && ((currentContainer.getqName().equals("decimalFormatLength"))
+ || (currentCalendarType != null && !currentCalendarType.lname().startsWith("islamic-")))) { // ignore islamic variants
pushAliasEntry(qName, attributes, attributes.getValue("path"));
} else {
pushIgnoredContainer(qName);
@@ -831,6 +904,9 @@
case "dayPeriods":
case "eras":
break;
+ case "decimalFormatLength": // used for compact number formatting patterns
+ keyName = type + ".CompactNumberPatterns";
+ break;
default:
keyName = "";
break;
@@ -869,6 +945,14 @@
width = path.substring(start+typeKey.length(), path.indexOf("']", start));
}
+ // used for compact number formatting patterns aliases
+ typeKey = "decimalFormatLength[@type='";
+ start = path.indexOf(typeKey);
+ if (start != -1) {
+ String style = path.substring(start + typeKey.length(), path.indexOf("']", start));
+ return toJDKKey(qName, "", style);
+ }
+
return calType + "." + toJDKKey(qName, context, width);
}
@@ -926,7 +1010,11 @@
currentContext = "";
putIfEntry();
break;
-
+ case "decimalFormatLength":
+ currentStyle = "";
+ compactCount = "";
+ putIfEntry();
+ break;
default:
putIfEntry();
}
@@ -937,22 +1025,28 @@
if (currentContainer instanceof AliasEntry) {
Entry<?> entry = (Entry<?>) currentContainer;
String containerqName = entry.getParent().getqName();
- Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth);
- if (!keyNames.isEmpty()) {
- for (String keyName : keyNames) {
- String[] tmp = keyName.split(",", 3);
- String calType = currentCalendarType.lname();
- String src = calType+"."+tmp[0];
- String target = getTarget(
- entry.getKey(),
- calType,
- tmp[1].length()>0 ? tmp[1] : currentContext,
- tmp[2].length()>0 ? tmp[2] : currentWidth);
- if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) {
- target = target.substring(0, target.indexOf('.'))+"."+tmp[0];
+ if (containerqName.equals("decimalFormatLength")) {
+ String srcKey = toJDKKey(containerqName, "", currentStyle);
+ String targetKey = getTarget(entry.getKey(), "", "", "");
+ CLDRConverter.aliases.put(srcKey, targetKey);
+ } else {
+ Set<String> keyNames = populateAliasKeys(containerqName, currentContext, currentWidth);
+ if (!keyNames.isEmpty()) {
+ for (String keyName : keyNames) {
+ String[] tmp = keyName.split(",", 3);
+ String calType = currentCalendarType.lname();
+ String src = calType+"."+tmp[0];
+ String target = getTarget(
+ entry.getKey(),
+ calType,
+ tmp[1].length()>0 ? tmp[1] : currentContext,
+ tmp[2].length()>0 ? tmp[2] : currentWidth);
+ if (target.substring(target.lastIndexOf('.')+1).equals(containerqName)) {
+ target = target.substring(0, target.indexOf('.'))+"."+tmp[0];
+ }
+ CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""),
+ target.replaceFirst("^gregorian.", ""));
}
- CLDRConverter.aliases.put(src.replaceFirst("^gregorian.", ""),
- target.replaceFirst("^gregorian.", ""));
}
}
} else if (currentContainer instanceof Entry) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/jdk/src/classes/build/tools/cldrconverter/StringListElement.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package build.tools.cldrconverter;
+
+class StringListElement extends Container {
+
+ StringListEntry list;
+ int index;
+
+ StringListElement(String qName, Container parent, int index) {
+ super(qName, parent);
+ while (!(parent instanceof StringListEntry)) {
+ parent = parent.getParent();
+ }
+ list = (StringListEntry) parent;
+ this.index = index;
+ }
+
+ @Override
+ void addCharacters(char[] characters, int start, int length) {
+ list.addCharacters(index, characters, start, length);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/jdk/src/classes/build/tools/cldrconverter/StringListEntry.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package build.tools.cldrconverter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+class StringListEntry extends Entry<List<String>> {
+
+ private List<String> value;
+
+ StringListEntry(String qName, Container parent, String key) {
+ super(qName, parent, key);
+ value = new ArrayList<>();
+ }
+
+ void addCharacters(int index, char[] characters, int start, int length) {
+ // fill with empty strings when the patterns start from index > 0
+ if (value.size() < index) {
+ IntStream.range(0, index).forEach(i -> value.add(i, ""));
+ value.add(index, new String(characters, start, length));
+ } else {
+ value.add(index, new String(characters, start, length));
+ }
+ }
+
+ @Override
+ List<String> getValue() {
+ for (String element : value) {
+ if (element != null) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,2130 @@
+/*
+ * 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;
+
+
+/**
+ * <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 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.length() > 0) {
+ 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) {
+
+ 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.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<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;
+ }
+
+}
+
--- a/src/java.base/share/classes/java/text/DecimalFormat.java Thu Dec 06 11:54:39 2018 +0530
+++ b/src/java.base/share/classes/java/text/DecimalFormat.java Thu Dec 06 12:39:28 2018 +0530
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -48,9 +48,6 @@
import java.util.ArrayList;
import java.util.Currency;
import java.util.Locale;
-import java.util.ResourceBundle;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import sun.util.locale.provider.LocaleProviderAdapter;
@@ -157,7 +154,7 @@
* used. So <code>"#,##,###,####"</code> == <code>"######,####"</code> ==
* <code>"##,####,####"</code>.
*
- * <h4>Special Pattern Characters</h4>
+ * <h4><a id="special_pattern_character">Special Pattern Characters</a></h4>
*
* <p>Many characters in a pattern are taken literally; they are matched during
* parsing and output unchanged during formatting. Special characters, on the
@@ -572,14 +569,11 @@
* mode being set to RoundingMode.UNNECESSARY
* @return The formatted number string
*/
- private StringBuffer format(double number, StringBuffer result,
+ StringBuffer format(double number, StringBuffer result,
FieldDelegate delegate) {
- if (Double.isNaN(number) ||
- (Double.isInfinite(number) && multiplier == 0)) {
- int iFieldStart = result.length();
- result.append(symbols.getNaN());
- delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER,
- iFieldStart, result.length(), result);
+
+ boolean nanOrInfinity = handleNaN(number, result, delegate);
+ if (nanOrInfinity) {
return result;
}
@@ -599,6 +593,56 @@
number *= multiplier;
}
+ nanOrInfinity = handleInfinity(number, result, delegate, isNegative);
+ if (nanOrInfinity) {
+ return result;
+ }
+
+ if (isNegative) {
+ number = -number;
+ }
+
+ // at this point we are guaranteed a nonnegative finite number.
+ assert (number >= 0 && !Double.isInfinite(number));
+ return doubleSubformat(number, result, delegate, isNegative);
+ }
+
+ /**
+ * Checks if the given {@code number} is {@code Double.NaN}. if yes;
+ * appends the NaN symbol to the result string. The NaN string is
+ * determined by the DecimalFormatSymbols object.
+ * @param number the double number to format
+ * @param result where the text is to be appended
+ * @param delegate notified of locations of sub fields
+ * @return true, if number is a NaN; false otherwise
+ */
+ boolean handleNaN(double number, StringBuffer result,
+ FieldDelegate delegate) {
+ if (Double.isNaN(number)
+ || (Double.isInfinite(number) && multiplier == 0)) {
+ int iFieldStart = result.length();
+ result.append(symbols.getNaN());
+ delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER,
+ iFieldStart, result.length(), result);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the given {@code number} is {@code Double.NEGATIVE_INFINITY}
+ * or {@code Double.POSITIVE_INFINITY}. if yes;
+ * appends the infinity string to the result string. The infinity string is
+ * determined by the DecimalFormatSymbols object.
+ * @param number the double number to format
+ * @param result where the text is to be appended
+ * @param delegate notified of locations of sub fields
+ * @param isNegative whether the given {@code number} is negative
+ * @return true, if number is a {@code Double.NEGATIVE_INFINITY} or
+ * {@code Double.POSITIVE_INFINITY}; false otherwise
+ */
+ boolean handleInfinity(double number, StringBuffer result,
+ FieldDelegate delegate, boolean isNegative) {
if (Double.isInfinite(number)) {
if (isNegative) {
append(result, negativePrefix, delegate,
@@ -621,27 +665,24 @@
getPositiveSuffixFieldPositions(), Field.SIGN);
}
- return result;
- }
-
- if (isNegative) {
- number = -number;
+ return true;
}
-
- // at this point we are guaranteed a nonnegative finite number.
- assert(number >= 0 && !Double.isInfinite(number));
-
- synchronized(digitList) {
+ return false;
+ }
+
+ StringBuffer doubleSubformat(double number, StringBuffer result,
+ FieldDelegate delegate, boolean isNegative) {
+ synchronized (digitList) {
int maxIntDigits = super.getMaximumIntegerDigits();
int minIntDigits = super.getMinimumIntegerDigits();
int maxFraDigits = super.getMaximumFractionDigits();
int minFraDigits = super.getMinimumFractionDigits();
- digitList.set(isNegative, number, useExponentialNotation ?
- maxIntDigits + maxFraDigits : maxFraDigits,
- !useExponentialNotation);
+ digitList.set(isNegative, number, useExponentialNotation
+ ? maxIntDigits + maxFraDigits : maxFraDigits,
+ !useExponentialNotation);
return subformat(result, delegate, isNegative, false,
- maxIntDigits, minIntDigits, maxFraDigits, minFraDigits);
+ maxIntDigits, minIntDigits, maxFraDigits, minFraDigits);
}
}
@@ -683,7 +724,7 @@
* mode being set to RoundingMode.UNNECESSARY
* @see java.text.FieldPosition
*/
- private StringBuffer format(long number, StringBuffer result,
+ StringBuffer format(long number, StringBuffer result,
FieldDelegate delegate) {
boolean isNegative = (number < 0);
if (isNegative) {
@@ -774,7 +815,7 @@
* mode being set to RoundingMode.UNNECESSARY
* @return The formatted number string
*/
- private StringBuffer format(BigDecimal number, StringBuffer result,
+ StringBuffer format(BigDecimal number, StringBuffer result,
FieldDelegate delegate) {
if (multiplier != 1) {
number = number.multiply(getBigDecimalMultiplier());
@@ -835,7 +876,7 @@
* mode being set to RoundingMode.UNNECESSARY
* @see java.text.FieldPosition
*/
- private StringBuffer format(BigInteger number, StringBuffer result,
+ StringBuffer format(BigInteger number, StringBuffer result,
FieldDelegate delegate, boolean formatLong) {
if (multiplier != 1) {
number = number.multiply(getBigIntegerMultiplier());
@@ -917,7 +958,7 @@
return delegate.getIterator(sb.toString());
}
- // ==== Begin fast-path formating logic for double =========================
+ // ==== Begin fast-path formatting logic for double =========================
/* Fast-path formatting will be used for format(double ...) methods iff a
* number of conditions are met (see checkAndSetFastPathStatus()):
@@ -1662,6 +1703,26 @@
}
+ /**
+ * Sets the {@code DigitList} used by this {@code DecimalFormat}
+ * instance.
+ * @param number the number to format
+ * @param isNegative true, if the number is negative; false otherwise
+ * @param maxDigits the max digits
+ */
+ void setDigitList(Number number, boolean isNegative, int maxDigits) {
+
+ if (number instanceof Double) {
+ digitList.set(isNegative, (Double) number, maxDigits, true);
+ } else if (number instanceof BigDecimal) {
+ digitList.set(isNegative, (BigDecimal) number, maxDigits, true);
+ } else if (number instanceof Long) {
+ digitList.set(isNegative, (Long) number, maxDigits);
+ } else if (number instanceof BigInteger) {
+ digitList.set(isNegative, (BigInteger) number, maxDigits);
+ }
+ }
+
// ======== End fast-path formating logic for double =========================
/**
@@ -1669,29 +1730,59 @@
* be filled in with the correct digits.
*/
private StringBuffer subformat(StringBuffer result, FieldDelegate delegate,
- boolean isNegative, boolean isInteger,
- int maxIntDigits, int minIntDigits,
- int maxFraDigits, int minFraDigits) {
- // NOTE: This isn't required anymore because DigitList takes care of this.
- //
- // // The negative of the exponent represents the number of leading
- // // zeros between the decimal and the first non-zero digit, for
- // // a value < 0.1 (e.g., for 0.00123, -fExponent == 2). If this
- // // is more than the maximum fraction digits, then we have an underflow
- // // for the printed representation. We recognize this here and set
- // // the DigitList representation to zero in this situation.
- //
- // if (-digitList.decimalAt >= getMaximumFractionDigits())
- // {
- // digitList.count = 0;
- // }
-
+ boolean isNegative, boolean isInteger,
+ int maxIntDigits, int minIntDigits,
+ int maxFraDigits, int minFraDigits) {
+
+ // Process prefix
+ if (isNegative) {
+ append(result, negativePrefix, delegate,
+ getNegativePrefixFieldPositions(), Field.SIGN);
+ } else {
+ append(result, positivePrefix, delegate,
+ getPositivePrefixFieldPositions(), Field.SIGN);
+ }
+
+ // Process number
+ subformatNumber(result, delegate, isNegative, isInteger,
+ maxIntDigits, minIntDigits, maxFraDigits, minFraDigits);
+
+ // Process suffix
+ if (isNegative) {
+ append(result, negativeSuffix, delegate,
+ getNegativeSuffixFieldPositions(), Field.SIGN);
+ } else {
+ append(result, positiveSuffix, delegate,
+ getPositiveSuffixFieldPositions(), Field.SIGN);
+ }
+
+ return result;
+ }
+
+ /**
+ * Subformats number part using the {@code DigitList} of this
+ * {@code DecimalFormat} instance.
+ * @param result where the text is to be appended
+ * @param delegate notified of the location of sub fields
+ * @param isNegative true, if the number is negative; false otherwise
+ * @param isInteger true, if the number is an integer; false otherwise
+ * @param maxIntDigits maximum integer digits
+ * @param minIntDigits minimum integer digits
+ * @param maxFraDigits maximum fraction digits
+ * @param minFraDigits minimum fraction digits
+ */
+ void subformatNumber(StringBuffer result, FieldDelegate delegate,
+ boolean isNegative, boolean isInteger,
+ int maxIntDigits, int minIntDigits,
+ int maxFraDigits, int minFraDigits) {
+
+ char grouping = symbols.getGroupingSeparator();
char zero = symbols.getZeroDigit();
int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero
- char grouping = symbols.getGroupingSeparator();
+
char decimal = isCurrencyFormat ?
- symbols.getMonetaryDecimalSeparator() :
- symbols.getDecimalSeparator();
+ symbols.getMonetaryDecimalSeparator() :
+ symbols.getDecimalSeparator();
/* Per bug 4147706, DecimalFormat must respect the sign of numbers which
* format as zero. This allows sensible computations and preserves
@@ -1703,14 +1794,6 @@
digitList.decimalAt = 0; // Normalize
}
- if (isNegative) {
- append(result, negativePrefix, delegate,
- getNegativePrefixFieldPositions(), Field.SIGN);
- } else {
- append(result, positivePrefix, delegate,
- getPositivePrefixFieldPositions(), Field.SIGN);
- }
-
if (useExponentialNotation) {
int iFieldStart = result.length();
int iFieldEnd = -1;
@@ -1719,7 +1802,6 @@
// Minimum integer digits are handled in exponential format by
// adjusting the exponent. For example, 0.01234 with 3 minimum
// integer digits is "123.4E-4".
-
// Maximum integer digits are interpreted as indicating the
// repeating range. This is useful for engineering notation, in
// which the exponent is restricted to a multiple of 3. For
@@ -1782,8 +1864,8 @@
fFieldStart = result.length();
}
result.append((i < digitList.count) ?
- (char)(digitList.digits[i] + zeroDelta) :
- zero);
+ (char)(digitList.digits[i] + zeroDelta) :
+ zero);
}
if (decimalSeparatorAlwaysShown && totalDigits == integerDigits) {
@@ -1802,17 +1884,17 @@
iFieldEnd = result.length();
}
delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER,
- iFieldStart, iFieldEnd, result);
+ iFieldStart, iFieldEnd, result);
if (addedDecimalSeparator) {
delegate.formatted(Field.DECIMAL_SEPARATOR,
- Field.DECIMAL_SEPARATOR,
- iFieldEnd, fFieldStart, result);
+ Field.DECIMAL_SEPARATOR,
+ iFieldEnd, fFieldStart, result);
}
if (fFieldStart == -1) {
fFieldStart = result.length();
}
delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION,
- fFieldStart, result.length(), result);
+ fFieldStart, result.length(), result);
// The exponent is output using the pattern-specified minimum
// exponent digits. There is no maximum limit to the exponent
@@ -1823,7 +1905,7 @@
result.append(symbols.getExponentSeparator());
delegate.formatted(Field.EXPONENT_SYMBOL, Field.EXPONENT_SYMBOL,
- fieldStart, result.length(), result);
+ fieldStart, result.length(), result);
// For zero values, we force the exponent to zero. We
// must do this here, and not earlier, because the value
@@ -1838,7 +1920,7 @@
fieldStart = result.length();
result.append(symbols.getMinusSign());
delegate.formatted(Field.EXPONENT_SIGN, Field.EXPONENT_SIGN,
- fieldStart, result.length(), result);
+ fieldStart, result.length(), result);
}
digitList.set(negativeExponent, exponent);
@@ -1849,10 +1931,10 @@
}
for (int i=0; i<digitList.decimalAt; ++i) {
result.append((i < digitList.count) ?
- (char)(digitList.digits[i] + zeroDelta) : zero);
+ (char)(digitList.digits[i] + zeroDelta) : zero);
}
delegate.formatted(Field.EXPONENT, Field.EXPONENT, eFieldStart,
- result.length(), result);
+ result.length(), result);
} else {
int iFieldStart = result.length();
@@ -1889,19 +1971,19 @@
// grouping separator if i==0 though; that's at the end of
// the integer part.
if (isGroupingUsed() && i>0 && (groupingSize != 0) &&
- (i % groupingSize == 0)) {
+ (i % groupingSize == 0)) {
int gStart = result.length();
result.append(grouping);
delegate.formatted(Field.GROUPING_SEPARATOR,
- Field.GROUPING_SEPARATOR, gStart,
- result.length(), result);
+ Field.GROUPING_SEPARATOR, gStart,
+ result.length(), result);
}
}
// Determine whether or not there are any printable fractional
// digits. If we've used up the digits we know there aren't.
boolean fractionPresent = (minFraDigits > 0) ||
- (!isInteger && digitIndex < digitList.count);
+ (!isInteger && digitIndex < digitList.count);
// If there is no fraction present, and we haven't printed any
// integer digits, then print a zero. Otherwise we won't print
@@ -1911,7 +1993,7 @@
}
delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER,
- iFieldStart, result.length(), result);
+ iFieldStart, result.length(), result);
// Output the decimal separator if we always do so.
int sStart = result.length();
@@ -1921,8 +2003,8 @@
if (sStart != result.length()) {
delegate.formatted(Field.DECIMAL_SEPARATOR,
- Field.DECIMAL_SEPARATOR,
- sStart, result.length(), result);
+ Field.DECIMAL_SEPARATOR,
+ sStart, result.length(), result);
}
int fFieldStart = result.length();
@@ -1934,7 +2016,7 @@
// we have an integer, so there is no fractional stuff to
// display, or we're out of significant digits.
if (i >= minFraDigits &&
- (isInteger || digitIndex >= digitList.count)) {
+ (isInteger || digitIndex >= digitList.count)) {
break;
}
@@ -1957,18 +2039,8 @@
// Record field information for caller.
delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION,
- fFieldStart, result.length(), result);
+ fFieldStart, result.length(), result);
}
-
- if (isNegative) {
- append(result, negativeSuffix, delegate,
- getNegativeSuffixFieldPositions(), Field.SIGN);
- } else {
- append(result, positiveSuffix, delegate,
- getPositiveSuffixFieldPositions(), Field.SIGN);
- }
-
- return result;
}
/**
@@ -2209,19 +2281,18 @@
* whether the value was infinite and whether it was positive.
*/
private final boolean subparse(String text, ParsePosition parsePosition,
- String positivePrefix, String negativePrefix,
- DigitList digits, boolean isExponent,
- boolean status[]) {
+ String positivePrefix, String negativePrefix,
+ DigitList digits, boolean isExponent,
+ boolean status[]) {
int position = parsePosition.index;
int oldStart = parsePosition.index;
- int backup;
boolean gotPositive, gotNegative;
// check for positivePrefix; take longest
gotPositive = text.regionMatches(position, positivePrefix, 0,
- positivePrefix.length());
+ positivePrefix.length());
gotNegative = text.regionMatches(position, negativePrefix, 0,
- negativePrefix.length());
+ negativePrefix.length());
if (gotPositive && gotNegative) {
if (positivePrefix.length() > negativePrefix.length()) {
@@ -2240,10 +2311,75 @@
return false;
}
+ position = subparseNumber(text, position, digits, true, isExponent, status);
+ if (position == -1) {
+ parsePosition.index = oldStart;
+ parsePosition.errorIndex = oldStart;
+ return false;
+ }
+
+ // Check for suffix
+ if (!isExponent) {
+ if (gotPositive) {
+ gotPositive = text.regionMatches(position,positiveSuffix,0,
+ positiveSuffix.length());
+ }
+ if (gotNegative) {
+ gotNegative = text.regionMatches(position,negativeSuffix,0,
+ negativeSuffix.length());
+ }
+
+ // If both match, take longest
+ if (gotPositive && gotNegative) {
+ if (positiveSuffix.length() > negativeSuffix.length()) {
+ gotNegative = false;
+ } else if (positiveSuffix.length() < negativeSuffix.length()) {
+ gotPositive = false;
+ }
+ }
+
+ // Fail if neither or both
+ if (gotPositive == gotNegative) {
+ parsePosition.errorIndex = position;
+ return false;
+ }
+
+ parsePosition.index = position +
+ (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success!
+ } else {
+ parsePosition.index = position;
+ }
+
+ status[STATUS_POSITIVE] = gotPositive;
+ if (parsePosition.index == oldStart) {
+ parsePosition.errorIndex = position;
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Parses a number from the given {@code text}. The text is parsed
+ * beginning at position, until an unparseable character is seen.
+ *
+ * @param text the string to parse
+ * @param position the position at which parsing begins
+ * @param digits the DigitList to set to the parsed value
+ * @param checkExponent whether to check for exponential number
+ * @param isExponent if the exponential part is encountered
+ * @param status upon return contains boolean status flags indicating
+ * whether the value is infinite and whether it is
+ * positive
+ * @return returns the position of the first unparseable character or
+ * -1 in case of no valid number parsed
+ */
+ int subparseNumber(String text, int position,
+ DigitList digits, boolean checkExponent,
+ boolean isExponent, boolean status[]) {
// process digits or Inf, find decimal position
status[STATUS_INFINITE] = false;
if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0,
- symbols.getInfinity().length())) {
+ symbols.getInfinity().length())) {
position += symbols.getInfinity().length();
status[STATUS_INFINITE] = true;
} else {
@@ -2257,8 +2393,8 @@
digits.decimalAt = digits.count = 0;
char zero = symbols.getZeroDigit();
char decimal = isCurrencyFormat ?
- symbols.getMonetaryDecimalSeparator() :
- symbols.getDecimalSeparator();
+ symbols.getMonetaryDecimalSeparator() :
+ symbols.getDecimalSeparator();
char grouping = symbols.getGroupingSeparator();
String exponentString = symbols.getExponentSeparator();
boolean sawDecimal = false;
@@ -2270,7 +2406,7 @@
// pin when the maximum allowable digits is reached.
int digitCount = 0;
- backup = -1;
+ int backup = -1;
for (; position < text.length(); ++position) {
char ch = text.charAt(position);
@@ -2334,15 +2470,15 @@
// require that they be followed by a digit. Otherwise
// we backup and reprocess them.
backup = position;
- } else if (!isExponent && text.regionMatches(position, exponentString, 0, exponentString.length())
- && !sawExponent) {
+ } else if (checkExponent && !isExponent && text.regionMatches(position, exponentString, 0, exponentString.length())
+ && !sawExponent) {
// Process the exponent by recursively calling this method.
- ParsePosition pos = new ParsePosition(position + exponentString.length());
+ ParsePosition pos = new ParsePosition(position + exponentString.length());
boolean[] stat = new boolean[STATUS_LENGTH];
DigitList exponentDigits = new DigitList();
if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) &&
- exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) {
+ exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) {
position = pos.index; // Advance past the exponent
exponent = (int)exponentDigits.getLong();
if (!stat[STATUS_POSITIVE]) {
@@ -2373,50 +2509,11 @@
// parse "$" with pattern "$#0.00". (return index 0 and error
// index 1).
if (!sawDigit && digitCount == 0) {
- parsePosition.index = oldStart;
- parsePosition.errorIndex = oldStart;
- return false;
+ return -1;
}
}
-
- // check for suffix
- if (!isExponent) {
- if (gotPositive) {
- gotPositive = text.regionMatches(position,positiveSuffix,0,
- positiveSuffix.length());
- }
- if (gotNegative) {
- gotNegative = text.regionMatches(position,negativeSuffix,0,
- negativeSuffix.length());
- }
-
- // if both match, take longest
- if (gotPositive && gotNegative) {
- if (positiveSuffix.length() > negativeSuffix.length()) {
- gotNegative = false;
- } else if (positiveSuffix.length() < negativeSuffix.length()) {
- gotPositive = false;
- }
- }
-
- // fail if neither or both
- if (gotPositive == gotNegative) {
- parsePosition.errorIndex = position;
- return false;
- }
-
- parsePosition.index = position +
- (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success!
- } else {
- parsePosition.index = position;
- }
-
- status[STATUS_POSITIVE] = gotPositive;
- if (parsePosition.index == oldStart) {
- parsePosition.errorIndex = position;
- return false;
- }
- return true;
+ return position;
+
}
/**
--- a/src/java.base/share/classes/java/text/NumberFormat.java Thu Dec 06 11:54:39 2018 +0530
+++ b/src/java.base/share/classes/java/text/NumberFormat.java Thu Dec 06 12:39:28 2018 +0530
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -47,13 +47,11 @@
import java.text.spi.NumberFormatProvider;
import java.util.Currency;
import java.util.HashMap;
-import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
-import java.util.ResourceBundle;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.spi.LocaleServiceProvider;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleServiceProviderPool;
@@ -112,9 +110,12 @@
* Use <code>getInstance</code> or <code>getNumberInstance</code> to get the
* normal number format. Use <code>getIntegerInstance</code> to get an
* integer number format. Use <code>getCurrencyInstance</code> to get the
- * currency number format. And use <code>getPercentInstance</code> to get a
- * format for displaying percentages. With this format, a fraction like
- * 0.53 is displayed as 53%.
+ * currency number format. Use {@code getCompactNumberInstance} to get the
+ * compact number format to format a number in shorter form. For example,
+ * {@code 2000} can be formatted as {@code "2K"} in
+ * {@link java.util.Locale#US US locale}. Use <code>getPercentInstance</code>
+ * to get a format for displaying percentages. With this format, a fraction
+ * like 0.53 is displayed as 53%.
*
* <p>
* You can also control the display of numbers with such methods as
@@ -122,9 +123,10 @@
* If you want even more control over the format or parsing,
* or want to give your users more control,
* you can try casting the <code>NumberFormat</code> you get from the factory methods
- * to a <code>DecimalFormat</code>. This will work for the vast majority
- * of locales; just remember to put it in a <code>try</code> block in case you
- * encounter an unusual one.
+ * to a {@code DecimalFormat} or {@code CompactNumberFormat} depending on
+ * the factory method used. This will work for the vast majority of locales;
+ * just remember to put it in a <code>try</code> block in case you encounter
+ * an unusual one.
*
* <p>
* NumberFormat and DecimalFormat are designed such that some controls
@@ -201,6 +203,7 @@
*
* @see DecimalFormat
* @see ChoiceFormat
+ * @see CompactNumberFormat
* @author Mark Davis
* @author Helena Shih
* @since 1.1
@@ -472,7 +475,7 @@
* formatting
*/
public static final NumberFormat getInstance() {
- return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE);
+ return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
}
/**
@@ -485,7 +488,7 @@
* formatting
*/
public static NumberFormat getInstance(Locale inLocale) {
- return getInstance(inLocale, NUMBERSTYLE);
+ return getInstance(inLocale, null, NUMBERSTYLE);
}
/**
@@ -501,7 +504,7 @@
* @see java.util.Locale.Category#FORMAT
*/
public static final NumberFormat getNumberInstance() {
- return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE);
+ return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
}
/**
@@ -512,7 +515,7 @@
* formatting
*/
public static NumberFormat getNumberInstance(Locale inLocale) {
- return getInstance(inLocale, NUMBERSTYLE);
+ return getInstance(inLocale, null, NUMBERSTYLE);
}
/**
@@ -534,7 +537,7 @@
* @since 1.4
*/
public static final NumberFormat getIntegerInstance() {
- return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE);
+ return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, INTEGERSTYLE);
}
/**
@@ -551,7 +554,7 @@
* @since 1.4
*/
public static NumberFormat getIntegerInstance(Locale inLocale) {
- return getInstance(inLocale, INTEGERSTYLE);
+ return getInstance(inLocale, null, INTEGERSTYLE);
}
/**
@@ -566,7 +569,7 @@
* @see java.util.Locale.Category#FORMAT
*/
public static final NumberFormat getCurrencyInstance() {
- return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE);
+ return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, CURRENCYSTYLE);
}
/**
@@ -576,7 +579,7 @@
* @return the {@code NumberFormat} instance for currency formatting
*/
public static NumberFormat getCurrencyInstance(Locale inLocale) {
- return getInstance(inLocale, CURRENCYSTYLE);
+ return getInstance(inLocale, null, CURRENCYSTYLE);
}
/**
@@ -591,7 +594,7 @@
* @see java.util.Locale.Category#FORMAT
*/
public static final NumberFormat getPercentInstance() {
- return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE);
+ return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, PERCENTSTYLE);
}
/**
@@ -601,14 +604,14 @@
* @return the {@code NumberFormat} instance for percentage formatting
*/
public static NumberFormat getPercentInstance(Locale inLocale) {
- return getInstance(inLocale, PERCENTSTYLE);
+ return getInstance(inLocale, null, PERCENTSTYLE);
}
/**
* Returns a scientific format for the current default locale.
*/
/*public*/ final static NumberFormat getScientificInstance() {
- return getInstance(Locale.getDefault(Locale.Category.FORMAT), SCIENTIFICSTYLE);
+ return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, SCIENTIFICSTYLE);
}
/**
@@ -617,7 +620,50 @@
* @param inLocale the desired locale
*/
/*public*/ static NumberFormat getScientificInstance(Locale inLocale) {
- return getInstance(inLocale, SCIENTIFICSTYLE);
+ return getInstance(inLocale, null, SCIENTIFICSTYLE);
+ }
+
+ /**
+ * Returns a compact number format for the default
+ * {@link java.util.Locale.Category#FORMAT FORMAT} locale with
+ * {@link NumberFormat.Style#SHORT "SHORT"} format style.
+ *
+ * @return A {@code NumberFormat} instance for compact number
+ * formatting
+ *
+ * @see CompactNumberFormat
+ * @see NumberFormat.Style
+ * @see java.util.Locale#getDefault(java.util.Locale.Category)
+ * @see java.util.Locale.Category#FORMAT
+ * @since 12
+ */
+ public static NumberFormat getCompactNumberInstance() {
+ return getInstance(Locale.getDefault(
+ Locale.Category.FORMAT), NumberFormat.Style.SHORT, COMPACTSTYLE);
+ }
+
+ /**
+ * Returns a compact number format for the specified {@link java.util.Locale locale}
+ * and {@link NumberFormat.Style formatStyle}.
+ *
+ * @param locale the desired locale
+ * @param formatStyle the style for formatting a number
+ * @return A {@code NumberFormat} instance for compact number
+ * formatting
+ * @throws NullPointerException if {@code locale} or {@code formatStyle}
+ * is {@code null}
+ *
+ * @see CompactNumberFormat
+ * @see NumberFormat.Style
+ * @see java.util.Locale
+ * @since 12
+ */
+ public static NumberFormat getCompactNumberInstance(Locale locale,
+ NumberFormat.Style formatStyle) {
+
+ Objects.requireNonNull(locale);
+ Objects.requireNonNull(formatStyle);
+ return getInstance(locale, formatStyle, COMPACTSTYLE);
}
/**
@@ -900,20 +946,22 @@
// =======================privates===============================
private static NumberFormat getInstance(Locale desiredLocale,
- int choice) {
+ Style formatStyle, int choice) {
LocaleProviderAdapter adapter;
adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
- desiredLocale);
- NumberFormat numberFormat = getInstance(adapter, desiredLocale, choice);
+ desiredLocale);
+ NumberFormat numberFormat = getInstance(adapter, desiredLocale,
+ formatStyle, choice);
if (numberFormat == null) {
numberFormat = getInstance(LocaleProviderAdapter.forJRE(),
- desiredLocale, choice);
+ desiredLocale, formatStyle, choice);
}
return numberFormat;
}
private static NumberFormat getInstance(LocaleProviderAdapter adapter,
- Locale locale, int choice) {
+ Locale locale, Style formatStyle,
+ int choice) {
NumberFormatProvider provider = adapter.getNumberFormatProvider();
NumberFormat numberFormat = null;
switch (choice) {
@@ -929,6 +977,9 @@
case INTEGERSTYLE:
numberFormat = provider.getIntegerInstance(locale);
break;
+ case COMPACTSTYLE:
+ numberFormat = provider.getCompactNumberInstance(locale, formatStyle);
+ break;
}
return numberFormat;
}
@@ -1001,6 +1052,7 @@
private static final int PERCENTSTYLE = 2;
private static final int SCIENTIFICSTYLE = 3;
private static final int INTEGERSTYLE = 4;
+ private static final int COMPACTSTYLE = 5;
/**
* True if the grouping (i.e. thousands) separator is used when
@@ -1276,5 +1328,43 @@
* Constant identifying the exponent sign field.
*/
public static final Field EXPONENT_SIGN = new Field("exponent sign");
+
+ /**
+ * Constant identifying the prefix field.
+ *
+ * @since 12
+ */
+ public static final Field PREFIX = new Field("prefix");
+
+ /**
+ * Constant identifying the suffix field.
+ *
+ * @since 12
+ */
+ public static final Field SUFFIX = new Field("suffix");
+ }
+
+ /**
+ * A number format style.
+ * <p>
+ * {@code Style} is an enum which represents the style for formatting
+ * a number within a given {@code NumberFormat} instance.
+ *
+ * @see CompactNumberFormat
+ * @see NumberFormat#getCompactNumberInstance(Locale, Style)
+ * @since 12
+ */
+ public enum Style {
+
+ /**
+ * The {@code SHORT} number format style.
+ */
+ SHORT,
+
+ /**
+ * The {@code LONG} number format style.
+ */
+ LONG
+
}
}
--- a/src/java.base/share/classes/java/text/spi/NumberFormatProvider.java Thu Dec 06 11:54:39 2018 +0530
+++ b/src/java.base/share/classes/java/text/spi/NumberFormatProvider.java Thu Dec 06 12:39:28 2018 +0530
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -110,4 +110,37 @@
* @see java.text.NumberFormat#getPercentInstance(java.util.Locale)
*/
public abstract NumberFormat getPercentInstance(Locale locale);
+
+ /**
+ * Returns a new {@code NumberFormat} instance which formats
+ * a number in its compact form for the specified
+ * {@code locale} and {@code formatStyle}.
+ *
+ * @implSpec The default implementation of this method throws
+ * {@code UnSupportedOperationException}. Overriding the implementation
+ * of this method returns the compact number formatter instance
+ * of the given {@code locale} with specified {@code formatStyle}.
+ *
+ * @param locale the desired locale
+ * @param formatStyle the style for formatting a number
+ * @throws NullPointerException if {@code locale} or {@code formatStyle}
+ * is {@code null}
+ * @throws IllegalArgumentException if {@code locale} is not
+ * one of the locales returned from
+ * {@link java.util.spi.LocaleServiceProvider#getAvailableLocales()
+ * getAvailableLocales()}.
+ * @return a compact number formatter
+ *
+ * @see java.text.NumberFormat#getCompactNumberInstance(Locale,
+ * NumberFormat.Style)
+ * @since 12
+ */
+ public NumberFormat getCompactNumberInstance(Locale locale,
+ NumberFormat.Style formatStyle) {
+ throw new UnsupportedOperationException(
+ "The " + this.getClass().getName() + " should override this"
+ + " method to return compact number format instance of "
+ + locale + " locale and " + formatStyle + " style.");
+ }
+
}
--- a/src/java.base/share/classes/sun/text/resources/FormatData.java Thu Dec 06 11:54:39 2018 +0530
+++ b/src/java.base/share/classes/sun/text/resources/FormatData.java Thu Dec 06 12:39:28 2018 +0530
@@ -796,6 +796,44 @@
"NaN",
}
},
+ { "short.CompactNumberPatterns",
+ new String[] {
+ "",
+ "",
+ "",
+ "0K",
+ "00K",
+ "000K",
+ "0M",
+ "00M",
+ "000M",
+ "0B",
+ "00B",
+ "000B",
+ "0T",
+ "00T",
+ "000T",
+ }
+ },
+ { "long.CompactNumberPatterns",
+ new String[] {
+ "",
+ "",
+ "",
+ "0 thousand",
+ "00 thousand",
+ "000 thousand",
+ "0 million",
+ "00 million",
+ "000 million",
+ "0 billion",
+ "00 billion",
+ "000 billion",
+ "0 trillion",
+ "00 trillion",
+ "000 trillion",
+ }
+ },
{ "TimePatterns",
new String[] {
"h:mm:ss a z", // full time pattern
--- a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java Thu Dec 06 11:54:39 2018 +0530
+++ b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java Thu Dec 06 12:39:28 2018 +0530
@@ -43,6 +43,7 @@
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.text.MessageFormat;
+import java.text.NumberFormat;
import java.util.Calendar;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -88,6 +89,7 @@
private static final String ZONE_IDS_CACHEKEY = "ZID";
private static final String CALENDAR_NAMES = "CALN.";
private static final String NUMBER_PATTERNS_CACHEKEY = "NP";
+ private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP";
private static final String DATE_TIME_PATTERN = "DTP.";
// TimeZoneNamesBundle exemplar city prefix
@@ -479,6 +481,32 @@
}
/**
+ * Returns the compact number format patterns.
+ * @param formatStyle the style for formatting a number
+ * @return an array of compact number patterns
+ */
+ @SuppressWarnings("unchecked")
+ public String[] getCNPatterns(NumberFormat.Style formatStyle) {
+
+ Objects.requireNonNull(formatStyle);
+ String[] compactNumberPatterns = null;
+ removeEmptyReferences();
+ String width = (formatStyle == NumberFormat.Style.LONG) ? "long" : "short";
+ String cacheKey = width + "." + COMPACT_NUMBER_PATTERNS_CACHEKEY;
+ ResourceReference data = cache.get(cacheKey);
+ if (data == null || ((compactNumberPatterns
+ = (String[]) data.get()) == null)) {
+ ResourceBundle resource = localeData.getNumberFormatData(locale);
+ compactNumberPatterns = (String[]) resource
+ .getObject(width + ".CompactNumberPatterns");
+ cache.put(cacheKey, new ResourceReference(cacheKey,
+ (Object) compactNumberPatterns, referenceQueue));
+ }
+ return compactNumberPatterns;
+ }
+
+
+ /**
* Returns the FormatData resource bundle of this LocaleResources.
* The FormatData should be used only for accessing extra
* resources required by JSR 310.
--- a/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java Thu Dec 06 11:54:39 2018 +0530
+++ b/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java Thu Dec 06 12:39:28 2018 +0530
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -40,12 +40,14 @@
package sun.util.locale.provider;
+import java.text.CompactNumberFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.spi.NumberFormatProvider;
import java.util.Currency;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
/**
@@ -225,6 +227,49 @@
}
}
+ /**
+ * Returns a new {@code NumberFormat} instance which formats
+ * a number in its compact form for the specified
+ * {@code locale} and {@code formatStyle}.
+ *
+ * @param locale the desired locale
+ * @param formatStyle the style for formatting a number
+ * @throws NullPointerException if {@code locale} or {@code formatStyle}
+ * is {@code null}
+ * @throws IllegalArgumentException if {@code locale} isn't
+ * one of the locales returned from
+ * {@link java.util.spi.LocaleServiceProvider#getAvailableLocales()
+ * getAvailableLocales()}.
+ * @return a compact number formatter
+ *
+ * @see java.text.NumberFormat#getCompactNumberInstance(Locale,
+ * NumberFormat.Style)
+ * @since 12
+ */
+ @Override
+ public NumberFormat getCompactNumberInstance(Locale locale,
+ NumberFormat.Style formatStyle) {
+
+ Objects.requireNonNull(locale);
+ Objects.requireNonNull(formatStyle);
+
+ // Check for region override
+ Locale override = locale.getUnicodeLocaleType("nu") == null
+ ? CalendarDataUtility.findRegionOverride(locale)
+ : locale;
+
+ LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type);
+ LocaleResources resource = adapter.getLocaleResources(override);
+
+ String[] numberPatterns = resource.getNumberPatterns();
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(override);
+ String[] cnPatterns = resource.getCNPatterns(formatStyle);
+
+ CompactNumberFormat format = new CompactNumberFormat(numberPatterns[0],
+ symbols, cnPatterns);
+ return format;
+ }
+
@Override
public Set<String> getAvailableLanguageTags() {
return langtags;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/CompactFormatAndParseHelper.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.math.BigDecimal;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import static org.testng.Assert.*;
+
+class CompactFormatAndParseHelper {
+
+ static void testFormat(NumberFormat cnf, Object number,
+ String expected) {
+ String result = cnf.format(number);
+ assertEquals(result, expected, "Incorrect formatting of the number '"
+ + number + "'");
+ }
+
+ static void testParse(NumberFormat cnf, String parseString,
+ Number expected, ParsePosition position, Class<? extends Number> returnType) throws ParseException {
+
+ Number number;
+ if (position == null) {
+ number = cnf.parse(parseString);
+ } else {
+ number = cnf.parse(parseString, position);
+ }
+
+ if (returnType != null) {
+ assertEquals(number.getClass(), returnType, "Incorrect return type for string" + parseString);
+ }
+
+ if (number instanceof Double) {
+ assertEquals(number.doubleValue(), (double) expected,
+ "Incorrect parsing of the string '" + parseString + "'");
+ } else if (number instanceof Long) {
+ assertEquals(number.longValue(), (long) expected,
+ "Incorrect parsing of the string '" + parseString + "'");
+ } else if (number instanceof BigDecimal) {
+ BigDecimal num = (BigDecimal) number;
+ assertEquals(num, (BigDecimal) expected,
+ "Incorrect parsing of the string '" + parseString + "'");
+ } else {
+ assertEquals(number, expected, "Incorrect parsing of the string '"
+ + parseString + "'");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestCNFRounding.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the rounding of formatted number in compact number formatting
+ * @run testng/othervm TestCNFRounding
+ */
+
+import java.math.RoundingMode;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.Locale;
+import static org.testng.Assert.*;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestCNFRounding {
+
+ private static final List<RoundingMode> MODES = List.of(
+ RoundingMode.HALF_EVEN,
+ RoundingMode.HALF_UP,
+ RoundingMode.HALF_DOWN,
+ RoundingMode.UP,
+ RoundingMode.DOWN,
+ RoundingMode.CEILING,
+ RoundingMode.FLOOR);
+
+ @DataProvider(name = "roundingData")
+ Object[][] roundingData() {
+ return new Object[][]{
+ // Number, half_even, half_up, half_down, up, down, ceiling, floor
+ {5500, new String[]{"6K", "6K", "5K", "6K", "5K", "6K", "5K"}},
+ {2500, new String[]{"2K", "3K", "2K", "3K", "2K", "3K", "2K"}},
+ {1600, new String[]{"2K", "2K", "2K", "2K", "1K", "2K", "1K"}},
+ {1100, new String[]{"1K", "1K", "1K", "2K", "1K", "2K", "1K"}},
+ {1000, new String[]{"1K", "1K", "1K", "1K", "1K", "1K", "1K"}},
+ {-1000, new String[]{"-1K", "-1K", "-1K", "-1K", "-1K", "-1K", "-1K"}},
+ {-1100, new String[]{"-1K", "-1K", "-1K", "-2K", "-1K", "-1K", "-2K"}},
+ {-1600, new String[]{"-2K", "-2K", "-2K", "-2K", "-1K", "-1K", "-2K"}},
+ {-2500, new String[]{"-2K", "-3K", "-2K", "-3K", "-2K", "-2K", "-3K"}},
+ {-5500, new String[]{"-6K", "-6K", "-5K", "-6K", "-5K", "-5K", "-6K"}},
+ {5501, new String[]{"6K", "6K", "6K", "6K", "5K", "6K", "5K"}},
+ {-5501, new String[]{"-6K", "-6K", "-6K", "-6K", "-5K", "-5K", "-6K"}},
+ {1001, new String[]{"1K", "1K", "1K", "2K", "1K", "2K", "1K"}},
+ {-1001, new String[]{"-1K", "-1K", "-1K", "-2K", "-1K", "-1K", "-2K"}},
+ {4501, new String[]{"5K", "5K", "5K", "5K", "4K", "5K", "4K"}},
+ {-4501, new String[]{"-5K", "-5K", "-5K", "-5K", "-4K", "-4K", "-5K"}},
+ {4500, new String[]{"4K", "5K", "4K", "5K", "4K", "5K", "4K"}},
+ {-4500, new String[]{"-4K", "-5K", "-4K", "-5K", "-4K", "-4K", "-5K"}},};
+ }
+
+ @DataProvider(name = "roundingFract")
+ Object[][] roundingFract() {
+ return new Object[][]{
+ // Number, half_even, half_up, half_down, up, down, ceiling, floor
+ {5550, new String[]{"5.5K", "5.5K", "5.5K", "5.6K", "5.5K", "5.6K", "5.5K"}},
+ {2550, new String[]{"2.5K", "2.5K", "2.5K", "2.6K", "2.5K", "2.6K", "2.5K"}},
+ {1660, new String[]{"1.7K", "1.7K", "1.7K", "1.7K", "1.6K", "1.7K", "1.6K"}},
+ {1110, new String[]{"1.1K", "1.1K", "1.1K", "1.2K", "1.1K", "1.2K", "1.1K"}},
+ {1000, new String[]{"1.0K", "1.0K", "1.0K", "1.0K", "1.0K", "1.0K", "1.0K"}},
+ {-1000, new String[]{"-1.0K", "-1.0K", "-1.0K", "-1.0K", "-1.0K", "-1.0K", "-1.0K"}},
+ {-1110, new String[]{"-1.1K", "-1.1K", "-1.1K", "-1.2K", "-1.1K", "-1.1K", "-1.2K"}},
+ {-1660, new String[]{"-1.7K", "-1.7K", "-1.7K", "-1.7K", "-1.6K", "-1.6K", "-1.7K"}},
+ {-2550, new String[]{"-2.5K", "-2.5K", "-2.5K", "-2.6K", "-2.5K", "-2.5K", "-2.6K"}},
+ {-5550, new String[]{"-5.5K", "-5.5K", "-5.5K", "-5.6K", "-5.5K", "-5.5K", "-5.6K"}},
+ {5551, new String[]{"5.6K", "5.6K", "5.6K", "5.6K", "5.5K", "5.6K", "5.5K"}},
+ {-5551, new String[]{"-5.6K", "-5.6K", "-5.6K", "-5.6K", "-5.5K", "-5.5K", "-5.6K"}},
+ {1001, new String[]{"1.0K", "1.0K", "1.0K", "1.1K", "1.0K", "1.1K", "1.0K"}},
+ {-1001, new String[]{"-1.0K", "-1.0K", "-1.0K", "-1.1K", "-1.0K", "-1.0K", "-1.1K"}},
+ {4551, new String[]{"4.6K", "4.6K", "4.6K", "4.6K", "4.5K", "4.6K", "4.5K"}},
+ {-4551, new String[]{"-4.6K", "-4.6K", "-4.6K", "-4.6K", "-4.5K", "-4.5K", "-4.6K"}},
+ {4500, new String[]{"4.5K", "4.5K", "4.5K", "4.5K", "4.5K", "4.5K", "4.5K"}},
+ {-4500, new String[]{"-4.5K", "-4.5K", "-4.5K", "-4.5K", "-4.5K", "-4.5K", "-4.5K"}},};
+ }
+
+ @DataProvider(name = "rounding2Fract")
+ Object[][] rounding2Fract() {
+ return new Object[][]{
+ // Number, half_even, half_up, half_down
+ {1115, new String[]{"1.11K", "1.11K", "1.11K"}},
+ {1125, new String[]{"1.12K", "1.13K", "1.12K"}},
+ {1135, new String[]{"1.14K", "1.14K", "1.14K"}},
+ {3115, new String[]{"3.12K", "3.12K", "3.12K"}},
+ {3125, new String[]{"3.12K", "3.13K", "3.12K"}},
+ {3135, new String[]{"3.13K", "3.13K", "3.13K"}},
+ {6865, new String[]{"6.87K", "6.87K", "6.87K"}},
+ {6875, new String[]{"6.88K", "6.88K", "6.87K"}},
+ {6885, new String[]{"6.88K", "6.88K", "6.88K"}},
+ {3124, new String[]{"3.12K", "3.12K", "3.12K"}},
+ {3126, new String[]{"3.13K", "3.13K", "3.13K"}},
+ {3128, new String[]{"3.13K", "3.13K", "3.13K"}},
+ {6864, new String[]{"6.86K", "6.86K", "6.86K"}},
+ {6865, new String[]{"6.87K", "6.87K", "6.87K"}},
+ {6868, new String[]{"6.87K", "6.87K", "6.87K"}},
+ {4685, new String[]{"4.68K", "4.68K", "4.68K"}},
+ {4687, new String[]{"4.69K", "4.69K", "4.69K"}},
+ {4686, new String[]{"4.69K", "4.69K", "4.69K"}},};
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullMode() {
+ NumberFormat fmt = NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+ fmt.setRoundingMode(null);
+ }
+
+ @Test
+ public void testDefaultRoundingMode() {
+ NumberFormat fmt = NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+ assertEquals(fmt.getRoundingMode(), RoundingMode.HALF_EVEN,
+ "Default RoundingMode should be " + RoundingMode.HALF_EVEN);
+ }
+
+ @Test(dataProvider = "roundingData")
+ public void testRounding(Object number, String[] expected) {
+ for (int index = 0; index < MODES.size(); index++) {
+ testRoundingMode(number, expected[index], 0, MODES.get(index));
+ }
+ }
+
+ @Test(dataProvider = "roundingFract")
+ public void testRoundingFract(Object number, String[] expected) {
+ for (int index = 0; index < MODES.size(); index++) {
+ testRoundingMode(number, expected[index], 1, MODES.get(index));
+ }
+ }
+
+ @Test(dataProvider = "rounding2Fract")
+ public void testRounding2Fract(Object number, String[] expected) {
+ List<RoundingMode> rModes = List.of(RoundingMode.HALF_EVEN,
+ RoundingMode.HALF_UP, RoundingMode.HALF_DOWN);
+ for (int index = 0; index < rModes.size(); index++) {
+ testRoundingMode(number, expected[index], 2, rModes.get(index));
+ }
+ }
+
+ private void testRoundingMode(Object number, String expected,
+ int fraction, RoundingMode rounding) {
+ NumberFormat fmt = NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+ fmt.setRoundingMode(rounding);
+ assertEquals(fmt.getRoundingMode(), rounding,
+ "RoundingMode set is not returned by getRoundingMode");
+
+ fmt.setMinimumFractionDigits(fraction);
+ String result = fmt.format(number);
+ assertEquals(result, expected, "Incorrect formatting of number "
+ + number + " using rounding mode: " + rounding);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestCompactNumber.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the functioning of compact number format
+ * @modules jdk.localedata
+ * @run testng/othervm TestCompactNumber
+ */
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+import java.util.stream.Stream;
+import static org.testng.Assert.*;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestCompactNumber {
+
+ private static final NumberFormat FORMAT_DZ_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("dz"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_EN_US_SHORT = NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+
+ private static final NumberFormat FORMAT_EN_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_HI_IN_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_JA_JP_SHORT = NumberFormat
+ .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT);
+
+ private static final NumberFormat FORMAT_IT_SHORT = NumberFormat
+ .getCompactNumberInstance(new Locale("it"), NumberFormat.Style.SHORT);
+
+ private static final NumberFormat FORMAT_CA_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("ca"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_AS_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("as"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_BRX_SHORT = NumberFormat
+ .getCompactNumberInstance(new Locale("brx"), NumberFormat.Style.SHORT);
+
+ private static final NumberFormat FORMAT_SW_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("sw"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_SE_SHORT = NumberFormat
+ .getCompactNumberInstance(new Locale("se"), NumberFormat.Style.SHORT);
+
+ @DataProvider(name = "format")
+ Object[][] compactFormatData() {
+ return new Object[][]{
+ // compact number format instance, number to format, formatted output
+ {FORMAT_DZ_LONG, 1000.09, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55"
+ + "\u0FB2\u0F42 \u0F21"},
+ {FORMAT_DZ_LONG, -999.99, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55"
+ + "\u0FB2\u0F42 \u0F21"},
+ {FORMAT_DZ_LONG, -0.0, "-\u0F20"},
+ {FORMAT_DZ_LONG, 3000L, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55"
+ + "\u0FB2\u0F42 \u0F23"},
+ {FORMAT_DZ_LONG, new BigInteger("12345678901234567890"), "\u0F51"
+ + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66"
+ + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"},
+ // negative
+ {FORMAT_DZ_LONG, new BigInteger("-12345678901234567890"), "-\u0F51"
+ + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66"
+ + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"},
+ {FORMAT_DZ_LONG, new BigDecimal("12345678901234567890.89"), "\u0F51"
+ + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66"
+ + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"},
+ {FORMAT_DZ_LONG, new BigDecimal("-12345678901234567890.89"), "-\u0F51"
+ + "\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66"
+ + "\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27"},
+ // Zeros
+ {FORMAT_EN_US_SHORT, 0, "0"},
+ {FORMAT_EN_US_SHORT, 0.0, "0"},
+ {FORMAT_EN_US_SHORT, -0.0, "-0"},
+ // Less than 1000 no suffix
+ {FORMAT_EN_US_SHORT, 499, "499"},
+ // Boundary number
+ {FORMAT_EN_US_SHORT, 1000.0, "1K"},
+ // Long
+ {FORMAT_EN_US_SHORT, 3000L, "3K"},
+ {FORMAT_EN_US_SHORT, 30000L, "30K"},
+ {FORMAT_EN_US_SHORT, 300000L, "300K"},
+ {FORMAT_EN_US_SHORT, 3000000L, "3M"},
+ {FORMAT_EN_US_SHORT, 30000000L, "30M"},
+ {FORMAT_EN_US_SHORT, 300000000L, "300M"},
+ {FORMAT_EN_US_SHORT, 3000000000L, "3B"},
+ {FORMAT_EN_US_SHORT, 30000000000L, "30B"},
+ {FORMAT_EN_US_SHORT, 300000000000L, "300B"},
+ {FORMAT_EN_US_SHORT, 3000000000000L, "3T"},
+ {FORMAT_EN_US_SHORT, 30000000000000L, "30T"},
+ {FORMAT_EN_US_SHORT, 300000000000000L, "300T"},
+ {FORMAT_EN_US_SHORT, 3000000000000000L, "3000T"},
+ // Negatives
+ {FORMAT_EN_US_SHORT, -3000L, "-3K"},
+ {FORMAT_EN_US_SHORT, -30000L, "-30K"},
+ {FORMAT_EN_US_SHORT, -300000L, "-300K"},
+ {FORMAT_EN_US_SHORT, -3000000L, "-3M"},
+ {FORMAT_EN_US_SHORT, -30000000L, "-30M"},
+ {FORMAT_EN_US_SHORT, -300000000L, "-300M"},
+ {FORMAT_EN_US_SHORT, -3000000000L, "-3B"},
+ {FORMAT_EN_US_SHORT, -30000000000L, "-30B"},
+ {FORMAT_EN_US_SHORT, -300000000000L, "-300B"},
+ {FORMAT_EN_US_SHORT, -3000000000000L, "-3T"},
+ {FORMAT_EN_US_SHORT, -30000000000000L, "-30T"},
+ {FORMAT_EN_US_SHORT, -300000000000000L, "-300T"},
+ {FORMAT_EN_US_SHORT, -3000000000000000L, "-3000T"},
+ // Double
+ {FORMAT_EN_US_SHORT, 3000.0, "3K"},
+ {FORMAT_EN_US_SHORT, 30000.0, "30K"},
+ {FORMAT_EN_US_SHORT, 300000.0, "300K"},
+ {FORMAT_EN_US_SHORT, 3000000.0, "3M"},
+ {FORMAT_EN_US_SHORT, 30000000.0, "30M"},
+ {FORMAT_EN_US_SHORT, 300000000.0, "300M"},
+ {FORMAT_EN_US_SHORT, 3000000000.0, "3B"},
+ {FORMAT_EN_US_SHORT, 30000000000.0, "30B"},
+ {FORMAT_EN_US_SHORT, 300000000000.0, "300B"},
+ {FORMAT_EN_US_SHORT, 3000000000000.0, "3T"},
+ {FORMAT_EN_US_SHORT, 30000000000000.0, "30T"},
+ {FORMAT_EN_US_SHORT, 300000000000000.0, "300T"},
+ {FORMAT_EN_US_SHORT, 3000000000000000.0, "3000T"},
+ // Negatives
+ {FORMAT_EN_US_SHORT, -3000.0, "-3K"},
+ {FORMAT_EN_US_SHORT, -30000.0, "-30K"},
+ {FORMAT_EN_US_SHORT, -300000.0, "-300K"},
+ {FORMAT_EN_US_SHORT, -3000000.0, "-3M"},
+ {FORMAT_EN_US_SHORT, -30000000.0, "-30M"},
+ {FORMAT_EN_US_SHORT, -300000000.0, "-300M"},
+ {FORMAT_EN_US_SHORT, -3000000000.0, "-3B"},
+ {FORMAT_EN_US_SHORT, -30000000000.0, "-30B"},
+ {FORMAT_EN_US_SHORT, -300000000000.0, "-300B"},
+ {FORMAT_EN_US_SHORT, -3000000000000.0, "-3T"},
+ {FORMAT_EN_US_SHORT, -30000000000000.0, "-30T"},
+ {FORMAT_EN_US_SHORT, -300000000000000.0, "-300T"},
+ {FORMAT_EN_US_SHORT, -3000000000000000.0, "-3000T"},
+ // BigInteger
+ {FORMAT_EN_US_SHORT, new BigInteger("12345678901234567890"),
+ "12345679T"},
+ {FORMAT_EN_US_SHORT, new BigInteger("-12345678901234567890"),
+ "-12345679T"},
+ //BigDecimal
+ {FORMAT_EN_US_SHORT, new BigDecimal("12345678901234567890.89"),
+ "12345679T"},
+ {FORMAT_EN_US_SHORT, new BigDecimal("-12345678901234567890.89"),
+ "-12345679T"},
+ {FORMAT_EN_US_SHORT, new BigDecimal("12345678901234567890123466767.89"),
+ "12345678901234568T"},
+ {FORMAT_EN_US_SHORT, new BigDecimal(
+ "12345678901234567890878732267863209.89"),
+ "12345678901234567890879T"},
+ // number as exponent
+ {FORMAT_EN_US_SHORT, 9.78313E+3, "10K"},
+ // Less than 1000 no suffix
+ {FORMAT_EN_LONG, 999, "999"},
+ // Round the value and then format
+ {FORMAT_EN_LONG, 999.99, "1 thousand"},
+ // 10 thousand
+ {FORMAT_EN_LONG, 99000, "99 thousand"},
+ // Long path
+ {FORMAT_EN_LONG, 330000, "330 thousand"},
+ // Double path
+ {FORMAT_EN_LONG, 3000.90, "3 thousand"},
+ // BigInteger path
+ {FORMAT_EN_LONG, new BigInteger("12345678901234567890"),
+ "12345679 trillion"},
+ //BigDecimal path
+ {FORMAT_EN_LONG, new BigDecimal("12345678901234567890.89"),
+ "12345679 trillion"},
+ // Less than 1000 no suffix
+ {FORMAT_HI_IN_LONG, -999, "-999"},
+ // Round the value with 0 fraction digits and format it
+ {FORMAT_HI_IN_LONG, -999.99, "-1 \u0939\u091C\u093C\u093E\u0930"},
+ // 10 thousand
+ {FORMAT_HI_IN_LONG, 99000, "99 \u0939\u091C\u093C\u093E\u0930"},
+ // Long path
+ {FORMAT_HI_IN_LONG, 330000, "3 \u0932\u093E\u0916"},
+ // Double path
+ {FORMAT_HI_IN_LONG, 3000.90, "3 \u0939\u091C\u093C\u093E\u0930"},
+ // BigInteger path
+ {FORMAT_HI_IN_LONG, new BigInteger("12345678901234567890"),
+ "123456789 \u0916\u0930\u092C"},
+ // BigDecimal path
+ {FORMAT_HI_IN_LONG, new BigDecimal("12345678901234567890.89"),
+ "123456789 \u0916\u0930\u092C"},
+ // 1000 does not have any suffix in "ja" locale
+ {FORMAT_JA_JP_SHORT, -999.99, "-1,000"},
+ // 0-9999 does not have any suffix
+ {FORMAT_JA_JP_SHORT, 9999, "9,999"},
+ // 99000/10000 => 9.9\u4E07 rounded to 10\u4E07
+ {FORMAT_JA_JP_SHORT, 99000, "10\u4E07"},
+ // Negative
+ {FORMAT_JA_JP_SHORT, -99000, "-10\u4E07"},
+ // Long path
+ {FORMAT_JA_JP_SHORT, 330000, "33\u4E07"},
+ // Double path
+ {FORMAT_JA_JP_SHORT, 3000.90, "3,001"},
+ // BigInteger path
+ {FORMAT_JA_JP_SHORT, new BigInteger("12345678901234567890"),
+ "12345679\u5146"},
+ // BigDecimal path
+ {FORMAT_JA_JP_SHORT, new BigDecimal("12345678901234567890.89"),
+ "12345679\u5146"},
+ // less than 1000 no suffix
+ {FORMAT_IT_SHORT, 499, "499"},
+ // Boundary number
+ {FORMAT_IT_SHORT, 1000, "1.000"},
+ // Long path
+ {FORMAT_IT_SHORT, 3000000L, "3\u00a0Mln"},
+ // Double path
+ {FORMAT_IT_SHORT, 3000000.0, "3\u00a0Mln"},
+ // BigInteger path
+ {FORMAT_IT_SHORT, new BigInteger("12345678901234567890"),
+ "12345679\u00a0Bln"},
+ // BigDecimal path
+ {FORMAT_IT_SHORT, new BigDecimal("12345678901234567890.89"),
+ "12345679\u00a0Bln"},
+ {FORMAT_CA_LONG, 999, "999"},
+ {FORMAT_CA_LONG, 999.99, "1 miler"},
+ {FORMAT_CA_LONG, 99000, "99 milers"},
+ {FORMAT_CA_LONG, 330000, "330 milers"},
+ {FORMAT_CA_LONG, 3000.90, "3 miler"},
+ {FORMAT_CA_LONG, 1000000, "1 mili\u00f3"},
+ {FORMAT_CA_LONG, new BigInteger("12345678901234567890"),
+ "12345679 bilions"},
+ {FORMAT_CA_LONG, new BigDecimal("12345678901234567890.89"),
+ "12345679 bilions"},
+ {FORMAT_AS_LONG, 5000.0, "\u09eb \u09b9\u09be\u099c\u09be\u09f0"},
+ {FORMAT_AS_LONG, 50000.0, "\u09eb\u09e6 \u09b9\u09be\u099c\u09be\u09f0"},
+ {FORMAT_AS_LONG, 500000.0, "\u09eb \u09b2\u09be\u0996"},
+ {FORMAT_AS_LONG, 5000000.0, "\u09eb \u09a8\u09bf\u09af\u09c1\u09a4"},
+ {FORMAT_AS_LONG, 50000000.0, "\u09eb\u09e6 \u09a8\u09bf\u09af\u09c1\u09a4"},
+ {FORMAT_AS_LONG, 500000000.0, "\u09eb\u09e6\u09e6 \u09a8\u09bf\u09af\u09c1\u09a4"},
+ {FORMAT_AS_LONG, 5000000000.0, "\u09eb \u09b6\u09a4 \u0995\u09cb\u099f\u09bf"},
+ {FORMAT_AS_LONG, 50000000000.0, "\u09eb\u09e6 \u09b6\u09a4 \u0995\u09cb\u099f\u09bf"},
+ {FORMAT_AS_LONG, 500000000000.0, "\u09eb\u09e6\u09e6 \u09b6\u09a4 \u0995\u09cb\u099f\u09bf"},
+ {FORMAT_AS_LONG, 5000000000000.0, "\u09eb \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"},
+ {FORMAT_AS_LONG, 50000000000000.0, "\u09eb\u09e6 \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"},
+ {FORMAT_AS_LONG, 500000000000000.0, "\u09eb\u09e6\u09e6 \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"},
+ {FORMAT_AS_LONG, 5000000000000000.0, "\u09eb\u09e6\u09e6\u09e6 \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"},
+ {FORMAT_AS_LONG, new BigInteger("12345678901234567890"),
+ "\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ef \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"},
+ {FORMAT_AS_LONG, new BigDecimal("12345678901234567890123466767.89"),
+ "\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ee \u09b6\u09a4 \u09aa\u09f0\u09be\u09f0\u09cd\u09a6\u09cd\u09a7"},
+ {FORMAT_BRX_SHORT, 999, "999"},
+ {FORMAT_BRX_SHORT, 999.99, "1K"},
+ {FORMAT_BRX_SHORT, 99000, "99K"},
+ {FORMAT_BRX_SHORT, 330000, "330K"},
+ {FORMAT_BRX_SHORT, 3000.90, "3K"},
+ {FORMAT_BRX_SHORT, 1000000, "1M"},
+ {FORMAT_BRX_SHORT, new BigInteger("12345678901234567890"),
+ "12345679T"},
+ {FORMAT_BRX_SHORT, new BigDecimal("12345678901234567890.89"),
+ "12345679T"},
+ // Less than 1000 no suffix
+ {FORMAT_SW_LONG, 499, "499"},
+ // Boundary number
+ {FORMAT_SW_LONG, 1000, "elfu 1"},
+ // Long path
+ {FORMAT_SW_LONG, 3000000L, "milioni 3"},
+ // Long path, negative
+ {FORMAT_SW_LONG, -3000000L, "milioni -3"},
+ // Double path
+ {FORMAT_SW_LONG, 3000000.0, "milioni 3"},
+ // Double path, negative
+ {FORMAT_SW_LONG, -3000000.0, "milioni -3"},
+ // BigInteger path
+ {FORMAT_SW_LONG, new BigInteger("12345678901234567890"),
+ "trilioni 12345679"},
+ // BigDecimal path
+ {FORMAT_SW_LONG, new BigDecimal("12345678901234567890.89"),
+ "trilioni 12345679"},
+ // Positives
+ // No compact form
+ {FORMAT_SE_SHORT, 999, "999"},
+ // Long
+ {FORMAT_SE_SHORT, 8000000L, "8\u00a0mn"},
+ // Double
+ {FORMAT_SE_SHORT, 8000.98, "8\u00a0dt"},
+ // Big integer
+ {FORMAT_SE_SHORT, new BigInteger("12345678901234567890"), "12345679\u00a0bn"},
+ // Big decimal
+ {FORMAT_SE_SHORT, new BigDecimal("12345678901234567890.98"), "12345679\u00a0bn"},
+ // Negatives
+ // No compact form
+ {FORMAT_SE_SHORT, -999, "\u2212999"},
+ // Long
+ {FORMAT_SE_SHORT, -8000000L, "\u22128\u00a0mn"},
+ // Double
+ {FORMAT_SE_SHORT, -8000.98, "\u22128\u00a0dt"},
+ // BigInteger
+ {FORMAT_SE_SHORT, new BigInteger("-12345678901234567890"), "\u221212345679\u00a0bn"},
+ // BigDecimal
+ {FORMAT_SE_SHORT, new BigDecimal("-12345678901234567890.98"), "\u221212345679\u00a0bn"},};
+ }
+
+ @DataProvider(name = "parse")
+ Object[][] compactParseData() {
+ return new Object[][]{
+ // compact number format instance, string to parse, parsed number, return type
+ {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F21", 1000L, Long.class},
+ {FORMAT_DZ_LONG, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F23", -3000L, Long.class},
+ {FORMAT_DZ_LONG, "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62"
+ + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21"
+ + "\u0F22\u0F23\u0F24\u0F25\u0F27", 1.23457E19, Double.class},
+ {FORMAT_DZ_LONG, "-\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62"
+ + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21"
+ + "\u0F22\u0F23\u0F24\u0F25\u0F27", -1.23457E19, Double.class},
+ {FORMAT_EN_US_SHORT, "-0.0", -0.0, Double.class},
+ {FORMAT_EN_US_SHORT, "-0", -0.0, Double.class},
+ {FORMAT_EN_US_SHORT, "0", 0L, Long.class},
+ {FORMAT_EN_US_SHORT, "499", 499L, Long.class},
+ {FORMAT_EN_US_SHORT, "-499", -499L, Long.class},
+ {FORMAT_EN_US_SHORT, "499.89", 499.89, Double.class},
+ {FORMAT_EN_US_SHORT, "-499.89", -499.89, Double.class},
+ {FORMAT_EN_US_SHORT, "1K", 1000L, Long.class},
+ {FORMAT_EN_US_SHORT, "-1K", -1000L, Long.class},
+ {FORMAT_EN_US_SHORT, "3K", 3000L, Long.class},
+ {FORMAT_EN_US_SHORT, "17K", 17000L, Long.class},
+ {FORMAT_EN_US_SHORT, "-17K", -17000L, Long.class},
+ {FORMAT_EN_US_SHORT, "-3K", -3000L, Long.class},
+ {FORMAT_EN_US_SHORT, "12345678901234567890", 1.2345678901234567E19, Double.class},
+ {FORMAT_EN_US_SHORT, "12345679T", 1.2345679E19, Double.class},
+ {FORMAT_EN_US_SHORT, "-12345679T", -1.2345679E19, Double.class},
+ {FORMAT_EN_US_SHORT, "599.01K", 599010L, Long.class},
+ {FORMAT_EN_US_SHORT, "-599.01K", -599010L, Long.class},
+ {FORMAT_EN_US_SHORT, "599444444.90T", 5.994444449E20, Double.class},
+ {FORMAT_EN_US_SHORT, "-599444444.90T", -5.994444449E20, Double.class},
+ {FORMAT_EN_US_SHORT, "123456789012345.5678K", 123456789012345568L, Long.class},
+ {FORMAT_EN_US_SHORT, "17.000K", 17000L, Long.class},
+ {FORMAT_EN_US_SHORT, "123.56678K", 123566.78000, Double.class},
+ {FORMAT_EN_US_SHORT, "-123.56678K", -123566.78000, Double.class},
+ {FORMAT_EN_LONG, "999", 999L, Long.class},
+ {FORMAT_EN_LONG, "1 thousand", 1000L, Long.class},
+ {FORMAT_EN_LONG, "3 thousand", 3000L, Long.class},
+ {FORMAT_EN_LONG, "12345679 trillion", 1.2345679E19, Double.class},
+ {FORMAT_HI_IN_LONG, "999", 999L, Long.class},
+ {FORMAT_HI_IN_LONG, "-999", -999L, Long.class},
+ {FORMAT_HI_IN_LONG, "1 \u0939\u091C\u093C\u093E\u0930", 1000L, Long.class},
+ {FORMAT_HI_IN_LONG, "-1 \u0939\u091C\u093C\u093E\u0930", -1000L, Long.class},
+ {FORMAT_HI_IN_LONG, "3 \u0939\u091C\u093C\u093E\u0930", 3000L, Long.class},
+ {FORMAT_HI_IN_LONG, "12345679 \u0916\u0930\u092C", 1234567900000000000L, Long.class},
+ {FORMAT_HI_IN_LONG, "-12345679 \u0916\u0930\u092C", -1234567900000000000L, Long.class},
+ {FORMAT_JA_JP_SHORT, "-99", -99L, Long.class},
+ {FORMAT_JA_JP_SHORT, "1\u4E07", 10000L, Long.class},
+ {FORMAT_JA_JP_SHORT, "30\u4E07", 300000L, Long.class},
+ {FORMAT_JA_JP_SHORT, "-30\u4E07", -300000L, Long.class},
+ {FORMAT_JA_JP_SHORT, "12345679\u5146", 1.2345679E19, Double.class},
+ {FORMAT_JA_JP_SHORT, "-12345679\u5146", -1.2345679E19, Double.class},
+ {FORMAT_IT_SHORT, "-99", -99L, Long.class},
+ {FORMAT_IT_SHORT, "1\u00a0Mln", 1000000L, Long.class},
+ {FORMAT_IT_SHORT, "30\u00a0Mln", 30000000L, Long.class},
+ {FORMAT_IT_SHORT, "-30\u00a0Mln", -30000000L, Long.class},
+ {FORMAT_IT_SHORT, "12345679\u00a0Bln", 1.2345679E19, Double.class},
+ {FORMAT_IT_SHORT, "-12345679\u00a0Bln", -1.2345679E19, Double.class},
+ {FORMAT_SW_LONG, "-0.0", -0.0, Double.class},
+ {FORMAT_SW_LONG, "499", 499L, Long.class},
+ {FORMAT_SW_LONG, "elfu 1", 1000L, Long.class},
+ {FORMAT_SW_LONG, "elfu 3", 3000L, Long.class},
+ {FORMAT_SW_LONG, "elfu 17", 17000L, Long.class},
+ {FORMAT_SW_LONG, "elfu -3", -3000L, Long.class},
+ {FORMAT_SW_LONG, "499", 499L, Long.class},
+ {FORMAT_SW_LONG, "-499", -499L, Long.class},
+ {FORMAT_SW_LONG, "elfu 1", 1000L, Long.class},
+ {FORMAT_SW_LONG, "elfu 3", 3000L, Long.class},
+ {FORMAT_SW_LONG, "elfu -3", -3000L, Long.class},
+ {FORMAT_SW_LONG, "elfu 17", 17000L, Long.class},
+ {FORMAT_SW_LONG, "trilioni 12345679", 1.2345679E19, Double.class},
+ {FORMAT_SW_LONG, "trilioni -12345679", -1.2345679E19, Double.class},
+ {FORMAT_SW_LONG, "elfu 599.01", 599010L, Long.class},
+ {FORMAT_SW_LONG, "elfu -599.01", -599010L, Long.class},
+ {FORMAT_SE_SHORT, "999", 999L, Long.class},
+ {FORMAT_SE_SHORT, "8\u00a0mn", 8000000L, Long.class},
+ {FORMAT_SE_SHORT, "8\u00a0dt", 8000L, Long.class},
+ {FORMAT_SE_SHORT, "12345679\u00a0bn", 1.2345679E19, Double.class},
+ {FORMAT_SE_SHORT, "12345679,89\u00a0bn", 1.2345679890000001E19, Double.class},
+ {FORMAT_SE_SHORT, "\u2212999", -999L, Long.class},
+ {FORMAT_SE_SHORT, "\u22128\u00a0mn", -8000000L, Long.class},
+ {FORMAT_SE_SHORT, "\u22128\u00a0dt", -8000L, Long.class},
+ {FORMAT_SE_SHORT, "\u221212345679\u00a0bn", -1.2345679E19, Double.class},
+ {FORMAT_SE_SHORT, "\u221212345679,89\u00a0bn", -1.2345679890000001E19, Double.class},};
+ }
+
+ @DataProvider(name = "exceptionParse")
+ Object[][] exceptionParseData() {
+ return new Object[][]{
+ // compact number instance, string to parse, null (no o/p; must throws exception)
+ // no number
+ {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42", null},
+ // Invalid prefix
+ {FORMAT_DZ_LONG, "-\u0F66\u0F9F\u0F7C\u0F44,\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F23", null},
+ // Invalid prefix for en_US
+ {FORMAT_EN_US_SHORT, "K12,347", null},
+ // Invalid prefix for ja_JP
+ {FORMAT_JA_JP_SHORT, "\u4E071", null},
+ // Localized minus sign should be used
+ {FORMAT_SE_SHORT, "-8\u00a0mn", null},};
+ }
+
+ @DataProvider(name = "invalidParse")
+ Object[][] invalidParseData() {
+ return new Object[][]{
+ // compact number instance, string to parse, parsed number
+ // Prefix and suffix do not match
+ {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F21 KM", 1000L},
+ // Exponents are unparseable
+ {FORMAT_EN_US_SHORT, "-1.05E4K", -1.05},
+ // Default instance does not allow grouping
+ {FORMAT_EN_US_SHORT, "12,347", 12L},
+ // Take partial suffix "K" as 1000 for en_US_SHORT patterns
+ {FORMAT_EN_US_SHORT, "12KM", 12000L},
+ // Invalid suffix
+ {FORMAT_HI_IN_LONG, "-1 \u00a0\u0915.", -1L},};
+ }
+
+ @DataProvider(name = "fieldPosition")
+ Object[][] formatFieldPositionData() {
+ return new Object[][]{
+ //compact number instance, number to format, field, start position, end position, formatted string
+ {FORMAT_DZ_LONG, -3500, NumberFormat.Field.SIGN, 0, 1, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"},
+ {FORMAT_DZ_LONG, 3500, NumberFormat.Field.INTEGER, 9, 10, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"},
+ {FORMAT_DZ_LONG, -3500, NumberFormat.Field.INTEGER, 10, 11, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"},
+ {FORMAT_DZ_LONG, 999, NumberFormat.Field.INTEGER, 0, 3, "\u0F29\u0F29\u0F29"},
+ {FORMAT_DZ_LONG, -999, NumberFormat.Field.INTEGER, 1, 4, "-\u0F29\u0F29\u0F29"},
+ {FORMAT_DZ_LONG, 3500, NumberFormat.Field.PREFIX, 0, 9, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"},
+ {FORMAT_DZ_LONG, -3500, NumberFormat.Field.PREFIX, 0, 10, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F24"},
+ {FORMAT_DZ_LONG, 999, NumberFormat.Field.PREFIX, 0, 0, "\u0F29\u0F29\u0F29"},
+ {FORMAT_EN_US_SHORT, -3500, NumberFormat.Field.SIGN, 0, 1, "-4K"},
+ {FORMAT_EN_US_SHORT, 3500, NumberFormat.Field.INTEGER, 0, 1, "4K"},
+ {FORMAT_EN_US_SHORT, 14900000067L, NumberFormat.Field.INTEGER, 0, 2, "15B"},
+ {FORMAT_EN_US_SHORT, -1000, NumberFormat.Field.PREFIX, 0, 1, "-1K"},
+ {FORMAT_EN_US_SHORT, 3500, NumberFormat.Field.SUFFIX, 1, 2, "4K"},
+ {FORMAT_EN_US_SHORT, 14900000067L, NumberFormat.Field.SUFFIX, 2, 3, "15B"},
+ {FORMAT_EN_LONG, 3500, NumberFormat.Field.INTEGER, 0, 1, "4 thousand"},
+ {FORMAT_EN_LONG, 14900000067L, NumberFormat.Field.INTEGER, 0, 2, "15 billion"},
+ {FORMAT_EN_LONG, 3500, NumberFormat.Field.SUFFIX, 1, 10, "4 thousand"},
+ {FORMAT_EN_LONG, 14900000067L, NumberFormat.Field.SUFFIX, 2, 10, "15 billion"},
+ {FORMAT_JA_JP_SHORT, 14900000067L, NumberFormat.Field.INTEGER, 0, 3, "149\u5104"},
+ {FORMAT_JA_JP_SHORT, -999.99, NumberFormat.Field.INTEGER, 1, 6, "-1,000"},
+ {FORMAT_JA_JP_SHORT, 14900000067L, NumberFormat.Field.SUFFIX, 3, 4, "149\u5104"},
+ {FORMAT_JA_JP_SHORT, -999.99, NumberFormat.Field.SUFFIX, 0, 0, "-1,000"},
+ {FORMAT_JA_JP_SHORT, -999.99, NumberFormat.Field.SIGN, 0, 1, "-1,000"},
+ {FORMAT_HI_IN_LONG, -14900000067L, NumberFormat.Field.SIGN, 0, 1,
+ "-15 \u0905\u0930\u092C"},
+ {FORMAT_HI_IN_LONG, 3500, NumberFormat.Field.INTEGER, 0, 1,
+ "4 \u0939\u091C\u093C\u093E\u0930"},
+ {FORMAT_HI_IN_LONG, 14900000067L, NumberFormat.Field.INTEGER, 0, 2,
+ "15 \u0905\u0930\u092C"},
+ {FORMAT_HI_IN_LONG, 3500, NumberFormat.Field.SUFFIX, 1, 7,
+ "4 \u0939\u091C\u093C\u093E\u0930"},
+ {FORMAT_HI_IN_LONG, 14900000067L, NumberFormat.Field.SUFFIX, 2, 6,
+ "15 \u0905\u0930\u092C"},
+ {FORMAT_SE_SHORT, 8000000L, NumberFormat.Field.SUFFIX, 1, 4, "8\u00a0mn"},
+ {FORMAT_SE_SHORT, 8000.98, NumberFormat.Field.SUFFIX, 1, 4, "8\u00a0dt"},
+ {FORMAT_SE_SHORT, new BigInteger("12345678901234567890"), NumberFormat.Field.SUFFIX, 8, 11, "12345679\u00a0bn"},
+ {FORMAT_SE_SHORT, new BigDecimal("12345678901234567890.98"), NumberFormat.Field.SUFFIX, 8, 11, "12345679\u00a0bn"},
+ {FORMAT_SE_SHORT, -8000000L, NumberFormat.Field.INTEGER, 1, 2, "\u22128\u00a0mn"},
+ {FORMAT_SE_SHORT, -8000.98, NumberFormat.Field.SIGN, 0, 1, "\u22128\u00a0dt"},
+ {FORMAT_SE_SHORT, new BigDecimal("-48982865901234567890.98"), NumberFormat.Field.INTEGER, 1, 9, "\u221248982866\u00a0bn"},};
+ }
+
+ @DataProvider(name = "varParsePosition")
+ Object[][] varParsePosition() {
+ return new Object[][]{
+ // compact number instance, parse string, parsed number,
+ // start position, end position, error index
+ {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F21 KM", 1000L, 0, 10, -1},
+ // Invalid prefix returns null
+ {FORMAT_DZ_LONG, "Number is: -\u0F66\u0F9F\u0F7C\u0F44,\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F23", null, 11, 11, 11},
+ // Returns null
+ {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42", null, 0, 0, 0},
+ {FORMAT_EN_US_SHORT, "Exponent: -1.05E4K", -1.05, 10, 15, -1},
+ // Default instance does not allow grouping
+ {FORMAT_EN_US_SHORT, "12,347", 12L, 0, 2, -1},
+ // Invalid suffix "KM" for en_US_SHORT patterns
+ {FORMAT_EN_US_SHORT, "12KM", 12000L, 0, 3, -1},
+ // Invalid suffix
+ {FORMAT_HI_IN_LONG, "-1 \u00a0\u0915.", -1L, 0, 2, -1},
+ {FORMAT_EN_LONG, "Number is: 12345679 trillion",
+ 1.2345679E19, 11, 28, -1},
+ {FORMAT_EN_LONG, "Number is: -12345679 trillion",
+ -1.2345679E19, 11, 29, -1},
+ {FORMAT_EN_LONG, "parse 12 thousand and four", 12000L, 6, 17, -1},};
+ }
+
+ @Test
+ public void testInstanceCreation() {
+ Stream.of(NumberFormat.getAvailableLocales()).forEach(l -> NumberFormat
+ .getCompactNumberInstance(l, NumberFormat.Style.SHORT).format(10000));
+ Stream.of(NumberFormat.getAvailableLocales()).forEach(l -> NumberFormat
+ .getCompactNumberInstance(l, NumberFormat.Style.LONG).format(10000));
+ }
+
+ @Test(dataProvider = "format")
+ public void testFormat(NumberFormat cnf, Object number,
+ String expected) {
+ CompactFormatAndParseHelper.testFormat(cnf, number, expected);
+ }
+
+ @Test(dataProvider = "parse")
+ public void testParse(NumberFormat cnf, String parseString,
+ Number expected, Class<? extends Number> returnType) throws ParseException {
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, returnType);
+ }
+
+ @Test(dataProvider = "parse")
+ public void testParsePosition(NumberFormat cnf, String parseString,
+ Number expected, Class<? extends Number> returnType) throws ParseException {
+ ParsePosition pos = new ParsePosition(0);
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, pos, returnType);
+ assertEquals(pos.getIndex(), parseString.length());
+ assertEquals(pos.getErrorIndex(), -1);
+ }
+
+ @Test(dataProvider = "varParsePosition")
+ public void testVarParsePosition(NumberFormat cnf, String parseString,
+ Number expected, int startPosition, int indexPosition,
+ int errPosition) throws ParseException {
+ ParsePosition pos = new ParsePosition(startPosition);
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, pos, null);
+ assertEquals(pos.getIndex(), indexPosition);
+ assertEquals(pos.getErrorIndex(), errPosition);
+ }
+
+ @Test(dataProvider = "exceptionParse", expectedExceptions = ParseException.class)
+ public void throwsParseException(NumberFormat cnf, String parseString,
+ Number expected) throws ParseException {
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null);
+ }
+
+ @Test(dataProvider = "invalidParse")
+ public void testInvalidParse(NumberFormat cnf, String parseString,
+ Number expected) throws ParseException {
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null);
+ }
+
+ @Test(dataProvider = "fieldPosition")
+ public void testFormatWithFieldPosition(NumberFormat nf,
+ Object number, Format.Field field, int posStartExpected,
+ int posEndExpected, String expected) {
+ FieldPosition pos = new FieldPosition(field);
+ StringBuffer buf = new StringBuffer();
+ StringBuffer result = nf.format(number, buf, pos);
+ assertEquals(result.toString(), expected, "Incorrect formatting of the number '"
+ + number + "'");
+ assertEquals(pos.getBeginIndex(), posStartExpected, "Incorrect start position"
+ + " while formatting the number '" + number + "', for the field " + field);
+ assertEquals(pos.getEndIndex(), posEndExpected, "Incorrect end position"
+ + " while formatting the number '" + number + "', for the field " + field);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestCompactPatternsValidity.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the validity of compact number patterns specified through
+ * CompactNumberFormat constructor
+ * @run testng/othervm TestCompactPatternsValidity
+ */
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.CompactNumberFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Locale;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestCompactPatternsValidity {
+
+ // Max range 10^4
+ private static final String[] COMPACT_PATTERN1 = new String[]{"0", "0", "0", "0K", "00K"};
+ // Quoted special character '.' as prefix
+ private static final String[] COMPACT_PATTERN2 = new String[]{"0", "'.'K0"};
+ // Quoted special character '.' as suffix
+ private static final String[] COMPACT_PATTERN3 = new String[]{"0", "0", "0", "0K", "00K'.'"};
+ // Containing both prefix and suffix
+ private static final String[] COMPACT_PATTERN4 = new String[]{"", "", "H0H", "0K", "00K", "H0G"};
+ // Differing while specifying prefix and suffix
+ private static final String[] COMPACT_PATTERN5 = new String[]{"", "", "", "0K", "K0"};
+ // Containing both prefix ('.') and suffix (K)
+ private static final String[] COMPACT_PATTERN6 = new String[]{"0", "", "", "'.'0K"};
+ // Quoted special character ',' as suffix
+ private static final String[] COMPACT_PATTERN7 = new String[]{"", "0", "0", "0K','"};
+ // Most commonly used type of compact patterns with 15 elements
+ private static final String[] COMPACT_PATTERN8 = new String[]{"", "", "", "0K", "00K", "000K", "0M",
+ "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"};
+ // All empty or special patterns; checking the default formatting behaviour
+ private static final String[] COMPACT_PATTERN9 = new String[]{"", "", "", "0", "0", "", "", "", "", "", "", "", "", "", ""};
+ // Patterns beyond 10^19; divisors beyond long range
+ private static final String[] COMPACT_PATTERN10 = new String[]{"", "", "", "0K", "00K", "000K", "0M", "00M",
+ "000M", "0B", "00B", "000B", "0T", "00T", "000T", "0L", "00L", "000L", "0XL", "00XL"};
+ // Containing positive;negative subpatterns
+ private static final String[] COMPACT_PATTERN11 = new String[]{"", "", "", "elfu 0;elfu -0", "elfu 00;elfu -00",
+ "elfu 000;elfu -000", "milioni 0;milioni -0", "milioni 00;milioni -00", "milioni 000;milioni -000"};
+ // Containing both prefix and suffix and positive;negative subpatern
+ private static final String[] COMPACT_PATTERN12 = new String[]{"", "", "H0H;H-0H", "0K;0K-", "00K;-00K", "H0G;-H0G"};
+
+ @DataProvider(name = "invalidPatterns")
+ Object[][] invalidCompactPatterns() {
+ return new Object[][]{
+ // compact patterns
+ // Pattern containing unquoted special character '.'
+ {new String[]{"", "", "", "0K", "00K."}},
+ // Pattern containing invalid single quote
+ {new String[]{"", "", "", "0 'do", "00K"}},
+ {new String[]{"", "", "", "0K", "00 don't"}},
+ // A non empty pattern containing no 0s (min integer digits)
+ {new String[]{"K", "0K", "00K"}},
+ // 0s (min integer digits) exceeding for the range at index 3
+ {new String[]{"", "", "0K", "00000K"}},};
+ }
+
+ @DataProvider(name = "validPatternsFormat")
+ Object[][] validPatternsFormat() {
+ return new Object[][]{
+ // compact patterns, numbers, expected output
+ {COMPACT_PATTERN1, List.of(200, 1000, 3000, 500000), List.of("200", "1K", "3K", "500K")},
+ {COMPACT_PATTERN2, List.of(1, 20, 3000), List.of("1", ".K2", ".K300")},
+ {COMPACT_PATTERN3, List.of(100.99, 1000, 30000), List.of("101", "1K", "30K.")},
+ {COMPACT_PATTERN4, List.of(0.0, 500, -500, 30000, 5000000), List.of("0", "H5H", "-H5H", "30K", "H50G")},
+ {COMPACT_PATTERN5, List.of(100, 1000, 30000), List.of("100", "1K", "K3")},
+ {COMPACT_PATTERN6, List.of(20.99, 1000, 30000), List.of("21", ".1K", ".30K")},
+ {COMPACT_PATTERN7, List.of(100, 1000, new BigInteger("12345678987654321")), List.of("100", "1K,", "12345678987654K,")},
+ {COMPACT_PATTERN8, List.of(new BigInteger("223565686837667632"), new BigDecimal("12322456774334.89766"), 30000, 3456.78),
+ List.of("223566T", "12T", "30K", "3K")},
+ {COMPACT_PATTERN9, List.of(new BigInteger("223566000000000000"), new BigDecimal("12345678987654567"), 30000, 3000),
+ List.of("223,566,000,000,000,000", "12,345,678,987,654,567", "30,000", "3,000")},
+ {COMPACT_PATTERN10, List.of(new BigInteger("100000000000000000"), new BigInteger("10000000000000000000"), new BigDecimal("555555555555555555555.89766"), 30000),
+ List.of("100L", "10XL", "556XL", "30K")},
+ {COMPACT_PATTERN11, List.of(20.99, -20.99, 1000, -1000, 30000, -30000, new BigInteger("12345678987654321"), new BigInteger("-12345678987654321")),
+ List.of("21", "-21", "elfu 1", "elfu -1", "elfu 30", "elfu -30", "milioni 12345678988", "milioni -12345678988")},
+ {COMPACT_PATTERN12, List.of(0, 500, -500, 30000, -3000, 5000000), List.of("0", "H5H", "H-5H", "30K", "3K-", "H50G")},};
+ }
+
+ @DataProvider(name = "validPatternsParse")
+ Object[][] validPatternsParse() {
+ return new Object[][]{
+ // compact patterns, parse string, expected output
+ {COMPACT_PATTERN1, List.of(".56", "200", ".1K", "3K", "500K"), List.of(0.56, 200L, 100L, 3000L, 500000L)},
+ {COMPACT_PATTERN2, List.of("1", ".K2", ".K300"), List.of(1L, 20L, 3000L)},
+ {COMPACT_PATTERN3, List.of("101", "1K", "30K."), List.of(101L, 1000L, 30000L)},
+ {COMPACT_PATTERN4, List.of("0", "H5H", "-H5H", "30K", "H50G"), List.of(0L, 500L, -500L, 30000L, 5000000L)},
+ {COMPACT_PATTERN5, List.of("100", "1K", "K3"), List.of(100L, 1000L, 30000L)},
+ {COMPACT_PATTERN6, List.of("21", ".1K", ".30K"), List.of(21L, 1000L, 30000L)},
+ {COMPACT_PATTERN7, List.of("100", "1K,", "12345678987654K,"), List.of(100L, 1000L, 12345678987654000L)},
+ {COMPACT_PATTERN8, List.of("223566T", "12T", "30K", "3K"), List.of(223566000000000000L, 12000000000000L, 30000L, 3000L)},
+ {COMPACT_PATTERN10, List.of("1L", "100L", "10XL", "556XL", "30K"), List.of(1000000000000000L, 100000000000000000L, 1.0E19, 5.56E20, 30000L)},
+ {COMPACT_PATTERN11, List.of("21", "-21", "100.90", "-100.90", "elfu 1", "elfu -1", "elfu 30", "elfu -30", "milioni 12345678988", "milioni -12345678988"),
+ List.of(21L, -21L, 100.90, -100.90, 1000L, -1000L, 30000L, -30000L, 12345678988000000L, -12345678988000000L)},
+ {COMPACT_PATTERN12, List.of("0", "H5H", "H-5H", "30K", "30K-", "H50G"), List.of(0L, 500L, -500L, 30000L, -30000L, 5000000L)},};
+ }
+
+ @Test(dataProvider = "invalidPatterns",
+ expectedExceptions = RuntimeException.class)
+ public void testInvalidCompactPatterns(String[] compactPatterns) {
+ new CompactNumberFormat("#,##0.0#", DecimalFormatSymbols
+ .getInstance(Locale.US), compactPatterns);
+ }
+
+ @Test(dataProvider = "validPatternsFormat")
+ public void testValidPatternsFormat(String[] compactPatterns,
+ List<Object> numbers, List<String> expected) {
+ CompactNumberFormat fmt = new CompactNumberFormat("#,##0.0#",
+ DecimalFormatSymbols.getInstance(Locale.US), compactPatterns);
+ for (int index = 0; index < numbers.size(); index++) {
+ CompactFormatAndParseHelper.testFormat(fmt, numbers.get(index),
+ expected.get(index));
+ }
+ }
+
+ @Test(dataProvider = "validPatternsParse")
+ public void testValidPatternsParse(String[] compactPatterns,
+ List<String> parseString, List<Number> numbers) throws ParseException {
+ CompactNumberFormat fmt = new CompactNumberFormat("#,##0.0#",
+ DecimalFormatSymbols.getInstance(Locale.US), compactPatterns);
+ for (int index = 0; index < parseString.size(); index++) {
+ CompactFormatAndParseHelper.testParse(fmt, parseString.get(index),
+ numbers.get(index), null, null);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestEquality.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the equals and hashCode method of CompactNumberFormat
+ * @modules jdk.localedata
+ * @run testng/othervm TestEquality
+ *
+ */
+
+import java.text.CompactNumberFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+import org.testng.annotations.Test;
+
+public class TestEquality {
+
+ @Test
+ public void testEquality() {
+ CompactNumberFormat cnf1 = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+
+ CompactNumberFormat cnf2 = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+
+ // A custom compact instance with the same state as
+ // compact number instance of "en_US" locale with SHORT style
+ String decimalPattern = "#,##0.###";
+ String[] compactPatterns = new String[]{"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"};
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Locale.US);
+ CompactNumberFormat cnf3 = new CompactNumberFormat(decimalPattern, symbols, compactPatterns);
+
+ // A compact instance created with different decimalPattern than cnf3
+ CompactNumberFormat cnf4 = new CompactNumberFormat("#,#0.0#", symbols, compactPatterns);
+
+ // A compact instance created with different format symbols than cnf3
+ CompactNumberFormat cnf5 = new CompactNumberFormat(decimalPattern,
+ DecimalFormatSymbols.getInstance(Locale.JAPAN), compactPatterns);
+
+ // A compact instance created with different compact patterns than cnf3
+ CompactNumberFormat cnf6 = new CompactNumberFormat(decimalPattern,
+ symbols, new String[]{"", "", "", "0K", "00K", "000K"});
+
+ // Checking reflexivity
+ if (!cnf1.equals(cnf1)) {
+ throw new RuntimeException("[testEquality() reflexivity FAILED: The compared"
+ + " objects must be equal]");
+ }
+
+ // Checking symmetry, checking equality of two same objects
+ if (!cnf1.equals(cnf2) || !cnf2.equals(cnf1)) {
+ throw new RuntimeException("[testEquality() symmetry FAILED: The compared"
+ + " objects must be equal]");
+ }
+
+ // Checking transitivity, three objects must be equal
+ if (!cnf1.equals(cnf2) || !cnf2.equals(cnf3) || !cnf1.equals(cnf3)) {
+ throw new RuntimeException("[testEquality() transitivity FAILED: The compared"
+ + " objects must be equal]");
+ }
+
+ // Objects must not be equal as the decimalPattern is different
+ checkEquals(cnf3, cnf4, false, "1st", "different decimal pattern");
+
+ // Objects must not be equal as the format symbols instance is different
+ checkEquals(cnf3, cnf5, false, "2nd", "different format symbols");
+
+ // Objects must not be equal as the compact patters are different
+ checkEquals(cnf3, cnf6, false, "3rd", "different compact patterns");
+
+ // Changing the min integer digits of first object; objects must not
+ // be equal
+ cnf1.setMinimumIntegerDigits(5);
+ checkEquals(cnf1, cnf2, false, "4th", "different min integer digits");
+
+ // Changing the min integer digits of second object; objects must
+ // be equal
+ cnf2.setMinimumIntegerDigits(5);
+ checkEquals(cnf1, cnf2, true, "5th", "");
+
+ // Changing the grouping size of first object; objects must not
+ // be equal
+ cnf1.setGroupingSize(4);
+ checkEquals(cnf1, cnf2, false, "6th", "different grouping size");
+
+ // Changing the grouping size if second object; objects must be equal
+ cnf2.setGroupingSize(4);
+ checkEquals(cnf1, cnf2, true, "7th", "");
+
+ // Changing the parseBigDecimal of first object; objects must not
+ // be equal
+ cnf1.setParseBigDecimal(true);
+ checkEquals(cnf1, cnf2, false, "8th", "different parse big decimal");
+
+ }
+
+ private void checkEquals(CompactNumberFormat cnf1, CompactNumberFormat cnf2,
+ boolean mustEqual, String nthComparison, String message) {
+ if (cnf1.equals(cnf2) != mustEqual) {
+ if (mustEqual) {
+ throw new RuntimeException("[testEquality() " + nthComparison
+ + " comparison FAILED: The compared objects must be equal]");
+ } else {
+ throw new RuntimeException("[testEquality() " + nthComparison
+ + " comparison FAILED: The compared objects must"
+ + " not be equal because of " + message + "]");
+ }
+ }
+ }
+
+ @Test
+ public void testHashCode() {
+ NumberFormat cnf1 = NumberFormat
+ .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT);
+ NumberFormat cnf2 = NumberFormat
+ .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT);
+
+ if (cnf1.hashCode() != cnf2.hashCode()) {
+ throw new RuntimeException("[testHashCode() FAILED: hashCode of the"
+ + " compared objects must match]");
+ }
+ }
+
+ // Test the property of equals and hashCode i.e. two equal object must
+ // always have the same hashCode
+ @Test
+ public void testEqualsAndHashCode() {
+ NumberFormat cnf1 = NumberFormat
+ .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.SHORT);
+ cnf1.setMinimumIntegerDigits(5);
+ NumberFormat cnf2 = NumberFormat
+ .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.SHORT);
+ cnf2.setMinimumIntegerDigits(5);
+ if (cnf1.equals(cnf2)) {
+ if (cnf1.hashCode() != cnf2.hashCode()) {
+ throw new RuntimeException("[testEqualsAndHashCode() FAILED: two"
+ + " equal objects must have same hashCode]");
+ }
+ } else {
+ throw new RuntimeException("[testEqualsAndHashCode() FAILED: The"
+ + " compared objects must be equal]");
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestFormatToCharacterIterator.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the functioning of
+ * CompactNumberFormat.formatToCharacterIterator method
+ * @modules jdk.localedata
+ * @run testng/othervm TestFormatToCharacterIterator
+ */
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.AttributedCharacterIterator;
+import java.text.CharacterIterator;
+import java.text.Format;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.Set;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestFormatToCharacterIterator {
+
+ private static final NumberFormat FORMAT_DZ = NumberFormat
+ .getCompactNumberInstance(new Locale("dz"),
+ NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_EN_US = NumberFormat
+ .getCompactNumberInstance(Locale.US,
+ NumberFormat.Style.SHORT);
+
+ private static final NumberFormat FORMAT_EN_LONG = NumberFormat
+ .getCompactNumberInstance(new Locale("en"),
+ NumberFormat.Style.LONG);
+
+ @DataProvider(name = "fieldPositions")
+ Object[][] compactFieldPositionData() {
+ return new Object[][]{
+ // compact format instance, number, resulted string, attributes/fields, attribute positions
+ {FORMAT_DZ, 1000.09, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F21",
+ new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 9, 9, 10}},
+ {FORMAT_DZ, -999.99, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F21",
+ new Format.Field[]{NumberFormat.Field.SIGN, NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER},
+ new int[]{0, 1, 1, 10, 10, 11}},
+ {FORMAT_DZ, -0.0, "-\u0F20", new Format.Field[]{NumberFormat.Field.SIGN, NumberFormat.Field.INTEGER}, new int[]{0, 1, 1, 2}},
+ {FORMAT_DZ, 3000L, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2\u0F42 \u0F23",
+ new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 9, 9, 10}},
+ {FORMAT_DZ, new BigInteger("12345678901234567890"),
+ "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27",
+ new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 14, 14, 20}},
+ {FORMAT_DZ, new BigDecimal("12345678901234567890.89"),
+ "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21\u0F22\u0F23\u0F24\u0F25\u0F27",
+ new Format.Field[]{NumberFormat.Field.PREFIX, NumberFormat.Field.INTEGER}, new int[]{0, 14, 14, 20}},
+ // Zeros
+ {FORMAT_EN_US, 0, "0", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 1}},
+ {FORMAT_EN_US, 0.0, "0", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 1}},
+ {FORMAT_EN_US, -0.0, "-0", new Format.Field[]{NumberFormat.Field.SIGN, NumberFormat.Field.INTEGER}, new int[]{0, 1, 1, 2}},
+ // Less than 1000 no suffix
+ {FORMAT_EN_US, 499, "499", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 3}},
+ // Boundary number
+ {FORMAT_EN_US, 1000.0, "1K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}},
+ // Long
+ {FORMAT_EN_US, 3000L, "3K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}},
+ {FORMAT_EN_US, 30000L, "30K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}},
+ {FORMAT_EN_US, 300000L, "300K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}},
+ {FORMAT_EN_US, 3000000L, "3M",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}},
+ {FORMAT_EN_US, 30000000L, "30M",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}},
+ {FORMAT_EN_US, 300000000L, "300M",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}},
+ {FORMAT_EN_US, 3000000000L, "3B",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}},
+ {FORMAT_EN_US, 30000000000L, "30B",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}},
+ {FORMAT_EN_US, 300000000000L, "300B",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}},
+ {FORMAT_EN_US, 3000000000000L, "3T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}},
+ {FORMAT_EN_US, 30000000000000L, "30T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}},
+ {FORMAT_EN_US, 300000000000000L, "300T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}},
+ {FORMAT_EN_US, 3000000000000000L, "3000T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 4, 4, 5}},
+ // Double
+ {FORMAT_EN_US, 3000.0, "3K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 2}},
+ {FORMAT_EN_US, 30000.0, "30K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}},
+ {FORMAT_EN_US, 300000.0, "300K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 4}},
+ {FORMAT_EN_US, 3000000000000000.0, "3000T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 4, 4, 5}},
+ // BigInteger
+ {FORMAT_EN_US, new BigInteger("12345678901234567890"), "12345679T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 9}},
+ // BigDecimal
+ {FORMAT_EN_US, new BigDecimal("12345678901234567890.89"), "12345679T",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 9}},
+ // Number as exponent
+ {FORMAT_EN_US, 9.78313E+3, "10K",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 3}},
+ // Less than 1000 no suffix
+ {FORMAT_EN_LONG, 999, "999", new Format.Field[]{NumberFormat.Field.INTEGER}, new int[]{0, 3}},
+ // Round the value and then format
+ {FORMAT_EN_LONG, 999.99, "1 thousand",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 10}},
+ // 10 thousand
+ {FORMAT_EN_LONG, 99000, "99 thousand",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 2, 2, 11}},
+ // Long path
+ {FORMAT_EN_LONG, 330000, "330 thousand",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 3, 3, 12}},
+ // Double path
+ {FORMAT_EN_LONG, 3000.90, "3 thousand",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 1, 1, 10}},
+ // BigInteger path
+ {FORMAT_EN_LONG, new BigInteger("12345678901234567890"), "12345679 trillion",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 17}},
+ // BigDecimal path
+ {FORMAT_EN_LONG, new BigDecimal("12345678901234567890.89"), "12345679 trillion",
+ new Format.Field[]{NumberFormat.Field.INTEGER, NumberFormat.Field.SUFFIX}, new int[]{0, 8, 8, 17}}
+ };
+ }
+
+ @Test(dataProvider = "fieldPositions")
+ public void testFormatToCharacterIterator(NumberFormat fmt, Object number,
+ String expected, Format.Field[] expectedFields, int[] positions) {
+ AttributedCharacterIterator iterator = fmt.formatToCharacterIterator(number);
+ assertEquals(getText(iterator), expected, "Incorrect formatting of the number '"
+ + number + "'");
+
+ iterator.first();
+ // Check start and end index of the formatted string
+ assertEquals(iterator.getBeginIndex(), 0, "Incorrect start index: "
+ + iterator.getBeginIndex() + " of the formatted string: " + expected);
+ assertEquals(iterator.getEndIndex(), expected.length(), "Incorrect end index: "
+ + iterator.getEndIndex() + " of the formatted string: " + expected);
+
+ // Check the attributes returned by the formatToCharacterIterator
+ assertEquals(iterator.getAllAttributeKeys(), Set.of(expectedFields),
+ "Attributes do not match while formatting number: " + number);
+
+ // Check the begin and end index for attributes
+ iterator.first();
+ int currentPosition = 0;
+ do {
+ int start = iterator.getRunStart();
+ int end = iterator.getRunLimit();
+ assertEquals(start, positions[currentPosition],
+ "Incorrect start position for the attribute(s): "
+ + iterator.getAttributes().keySet());
+ assertEquals(end, positions[currentPosition + 1],
+ "Incorrect end position for the attribute(s): "
+ + iterator.getAttributes().keySet());
+ currentPosition = currentPosition + 2;
+ iterator.setIndex(end);
+ } while (iterator.current() != CharacterIterator.DONE);
+ }
+
+ // Create the formatted string from returned AttributedCharacterIterator
+ private String getText(AttributedCharacterIterator iterator) {
+ StringBuffer buffer = new StringBuffer();
+ for (char c = iterator.first(); c != CharacterIterator.DONE;
+ c = iterator.next()) {
+ buffer.append(c);
+ }
+ return buffer.toString();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestMutatingInstance.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the functioning of compact number format by changing the
+ * formatting parameters. For example, min fraction digits, grouping
+ * size etc.
+ * @modules jdk.localedata
+ * @run testng/othervm TestMutatingInstance
+ */
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.CompactNumberFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestMutatingInstance {
+
+ private static final NumberFormat FORMAT_FRACTION = NumberFormat
+ .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG);
+
+ private static final CompactNumberFormat FORMAT_GROUPING = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_MININTEGER = NumberFormat
+ .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG);
+
+ private static final NumberFormat FORMAT_PARSEINTONLY = NumberFormat
+ .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG);
+
+ // No compact patterns are specified for this instance except at index 4.
+ // This is to test how the behaviour differs between compact number formatting
+ // and general number formatting
+ private static final NumberFormat FORMAT_NO_PATTERNS = new CompactNumberFormat(
+ "#,##0.0#", DecimalFormatSymbols.getInstance(Locale.US),
+ new String[]{"", "", "", "", "00K", "", "", "", "", "", "", "", "", "", ""});
+
+ @BeforeTest
+ public void mutateInstances() {
+ FORMAT_FRACTION.setMinimumFractionDigits(2);
+ FORMAT_GROUPING.setGroupingSize(3);
+ FORMAT_GROUPING.setGroupingUsed(true);
+ FORMAT_MININTEGER.setMinimumIntegerDigits(5);
+ FORMAT_PARSEINTONLY.setParseIntegerOnly(true);
+ FORMAT_PARSEINTONLY.setGroupingUsed(true);
+ // Setting min fraction digits and other fields does not effect
+ // the general number formatting behaviour, when no compact number
+ // patterns are specified
+ FORMAT_NO_PATTERNS.setMinimumFractionDigits(2);
+ }
+
+ @DataProvider(name = "format")
+ Object[][] compactFormatData() {
+ return new Object[][]{
+ {FORMAT_FRACTION, 1900, "1.90 thousand"},
+ {FORMAT_FRACTION, 1000, "1.00 thousand"},
+ {FORMAT_FRACTION, 9090.99, "9.09 thousand"},
+ {FORMAT_FRACTION, new BigDecimal(12346567890987654.32),
+ "12346.57 trillion"},
+ {FORMAT_FRACTION, new BigInteger("12346567890987654"),
+ "12346.57 trillion"},
+ {FORMAT_GROUPING, new BigDecimal(12346567890987654.32),
+ "12,347 trillion"},
+ {FORMAT_GROUPING, 100000, "100 thousand"},
+ {FORMAT_MININTEGER, 10000, "00010 thousand"},
+ {FORMAT_NO_PATTERNS, 100000, "100,000"},
+ {FORMAT_NO_PATTERNS, 1000.998, "1,001"},
+ {FORMAT_NO_PATTERNS, 10900, "10.90K"},
+ {FORMAT_NO_PATTERNS, new BigDecimal(12346567890987654.32), "12,346,567,890,987,654"},};
+ }
+
+ @DataProvider(name = "parse")
+ Object[][] compactParseData() {
+ return new Object[][]{
+ {FORMAT_FRACTION, "190 thousand", 190000L},
+ {FORMAT_FRACTION, "19.9 thousand", 19900L},
+ {FORMAT_GROUPING, "12,346 thousand", 12346000L},
+ {FORMAT_PARSEINTONLY, "12345 thousand", 12345000L},
+ {FORMAT_PARSEINTONLY, "12,345 thousand", 12345000L},
+ {FORMAT_PARSEINTONLY, "12.345 thousand", 12000L},};
+ }
+
+ @Test(dataProvider = "format")
+ public void formatCompactNumber(NumberFormat nf,
+ Object number, String expected) {
+ CompactFormatAndParseHelper.testFormat(nf, number, expected);
+ }
+
+ @Test(dataProvider = "parse")
+ public void parseCompactNumber(NumberFormat nf,
+ String parseString, Number expected) throws ParseException {
+ CompactFormatAndParseHelper.testParse(nf, parseString, expected, null, null);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestParseBigDecimal.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks CNF.parse() when parseBigDecimal is set to true
+ * @modules jdk.localedata
+ * @run testng/othervm TestParseBigDecimal
+ */
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.text.CompactNumberFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+public class TestParseBigDecimal {
+
+ private static final CompactNumberFormat FORMAT_DZ_LONG = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("dz"), NumberFormat.Style.LONG);
+
+ private static final CompactNumberFormat FORMAT_EN_US_SHORT = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+
+ private static final CompactNumberFormat FORMAT_EN_LONG = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG);
+
+ private static final CompactNumberFormat FORMAT_HI_IN_LONG = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("hi", "IN"), NumberFormat.Style.LONG);
+
+ private static final CompactNumberFormat FORMAT_JA_JP_SHORT = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(Locale.JAPAN, NumberFormat.Style.SHORT);
+
+ private static final CompactNumberFormat FORMAT_IT_SHORT = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("it"), NumberFormat.Style.SHORT);
+
+ private static final CompactNumberFormat FORMAT_SW_LONG = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("sw"), NumberFormat.Style.LONG);
+
+ private static final CompactNumberFormat FORMAT_SE_SHORT = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(new Locale("se"), NumberFormat.Style.SHORT);
+
+ @BeforeTest
+ public void mutateInstances() {
+ FORMAT_DZ_LONG.setParseBigDecimal(true);
+ FORMAT_EN_US_SHORT.setParseBigDecimal(true);
+ FORMAT_EN_LONG.setParseBigDecimal(true);
+ FORMAT_HI_IN_LONG.setParseBigDecimal(true);
+ FORMAT_JA_JP_SHORT.setParseBigDecimal(true);
+ FORMAT_IT_SHORT.setParseBigDecimal(true);
+ FORMAT_SW_LONG.setParseBigDecimal(true);
+ FORMAT_SE_SHORT.setParseBigDecimal(true);
+ }
+
+ @DataProvider(name = "parse")
+ Object[][] compactParseData() {
+ return new Object[][]{
+ // compact number format instance, string to parse, parsed number
+ {FORMAT_DZ_LONG, "\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F21", new BigDecimal("1000")},
+ {FORMAT_DZ_LONG, "-\u0F66\u0F9F\u0F7C\u0F44\u0F0B\u0F55\u0FB2"
+ + "\u0F42 \u0F23", new BigDecimal("-3000")},
+ {FORMAT_DZ_LONG, "\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62"
+ + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21"
+ + "\u0F22\u0F23\u0F24\u0F25\u0F27", new BigDecimal("12345700000000000000")},
+ {FORMAT_DZ_LONG, "-\u0F51\u0F74\u0F44\u0F0B\u0F55\u0FB1\u0F74\u0F62"
+ + "\u0F0B\u0F66\u0F0B\u0F61\u0F0B \u0F21"
+ + "\u0F22\u0F23\u0F24\u0F25\u0F27", new BigDecimal("-12345700000000000000")},
+ {FORMAT_EN_US_SHORT, "-0.0", new BigDecimal("-0.0")},
+ {FORMAT_EN_US_SHORT, "0", new BigDecimal("0")},
+ {FORMAT_EN_US_SHORT, "499", new BigDecimal("499")},
+ {FORMAT_EN_US_SHORT, "-499", new BigDecimal("-499")},
+ {FORMAT_EN_US_SHORT, "499.89", new BigDecimal("499.89")},
+ {FORMAT_EN_US_SHORT, "-499.89", new BigDecimal("-499.89")},
+ {FORMAT_EN_US_SHORT, "1K", new BigDecimal("1000")},
+ {FORMAT_EN_US_SHORT, "-1K", new BigDecimal("-1000")},
+ {FORMAT_EN_US_SHORT, "3K", new BigDecimal("3000")},
+ {FORMAT_EN_US_SHORT, "-3K", new BigDecimal("-3000")},
+ {FORMAT_EN_US_SHORT, "17K", new BigDecimal("17000")},
+ {FORMAT_EN_US_SHORT, "-17K", new BigDecimal("-17000")},
+ {FORMAT_EN_US_SHORT, "12345678901234567890",
+ new BigDecimal("12345678901234567890")},
+ {FORMAT_EN_US_SHORT, "12345679T", new BigDecimal("12345679000000000000")},
+ {FORMAT_EN_US_SHORT, "-12345679T", new BigDecimal("-12345679000000000000")},
+ {FORMAT_EN_US_SHORT, "599.01K", new BigDecimal("599010.00")},
+ {FORMAT_EN_US_SHORT, "-599.01K", new BigDecimal("-599010.00")},
+ {FORMAT_EN_US_SHORT, "599444444.90T", new BigDecimal("599444444900000000000.00")},
+ {FORMAT_EN_US_SHORT, "-599444444.90T", new BigDecimal("-599444444900000000000.00")},
+ {FORMAT_EN_US_SHORT, "123456789012345.5678K",
+ new BigDecimal("123456789012345567.8000")},
+ {FORMAT_EN_US_SHORT, "17.000K", new BigDecimal("17000.000")},
+ {FORMAT_EN_US_SHORT, "123.56678K", new BigDecimal("123566.78000")},
+ {FORMAT_EN_US_SHORT, "-123.56678K", new BigDecimal("-123566.78000")},
+ {FORMAT_EN_LONG, "999", new BigDecimal("999")},
+ {FORMAT_EN_LONG, "1 thousand", new BigDecimal("1000")},
+ {FORMAT_EN_LONG, "3 thousand", new BigDecimal("3000")},
+ {FORMAT_EN_LONG, "12345679 trillion", new BigDecimal("12345679000000000000")},
+ {FORMAT_HI_IN_LONG, "999", new BigDecimal("999")},
+ {FORMAT_HI_IN_LONG, "-999", new BigDecimal("-999")},
+ {FORMAT_HI_IN_LONG, "1 \u0939\u091C\u093C\u093E\u0930", new BigDecimal("1000")},
+ {FORMAT_HI_IN_LONG, "-1 \u0939\u091C\u093C\u093E\u0930", new BigDecimal("-1000")},
+ {FORMAT_HI_IN_LONG, "3 \u0939\u091C\u093C\u093E\u0930", new BigDecimal("3000")},
+ {FORMAT_HI_IN_LONG, "12345679 \u0916\u0930\u092C", new BigDecimal("1234567900000000000")},
+ {FORMAT_HI_IN_LONG, "-12345679 \u0916\u0930\u092C", new BigDecimal("-1234567900000000000")},
+ {FORMAT_JA_JP_SHORT, "-99", new BigDecimal("-99")},
+ {FORMAT_JA_JP_SHORT, "1\u4E07", new BigDecimal("10000")},
+ {FORMAT_JA_JP_SHORT, "30\u4E07", new BigDecimal("300000")},
+ {FORMAT_JA_JP_SHORT, "-30\u4E07", new BigDecimal("-300000")},
+ {FORMAT_JA_JP_SHORT, "12345679\u5146", new BigDecimal("12345679000000000000")},
+ {FORMAT_JA_JP_SHORT, "-12345679\u5146", new BigDecimal("-12345679000000000000")},
+ {FORMAT_IT_SHORT, "-99", new BigDecimal("-99")},
+ {FORMAT_IT_SHORT, "1\u00a0Mln", new BigDecimal("1000000")},
+ {FORMAT_IT_SHORT, "30\u00a0Mln", new BigDecimal("30000000")},
+ {FORMAT_IT_SHORT, "-30\u00a0Mln", new BigDecimal("-30000000")},
+ {FORMAT_IT_SHORT, "12345679\u00a0Bln", new BigDecimal("12345679000000000000")},
+ {FORMAT_IT_SHORT, "-12345679\u00a0Bln", new BigDecimal("-12345679000000000000")},
+ {FORMAT_SW_LONG, "-0.0", new BigDecimal("-0.0")},
+ {FORMAT_SW_LONG, "499", new BigDecimal("499")},
+ {FORMAT_SW_LONG, "elfu 1", new BigDecimal("1000")},
+ {FORMAT_SW_LONG, "elfu 3", new BigDecimal("3000")},
+ {FORMAT_SW_LONG, "elfu 17", new BigDecimal("17000")},
+ {FORMAT_SW_LONG, "elfu -3", new BigDecimal("-3000")},
+ {FORMAT_SW_LONG, "-499", new BigDecimal("-499")},
+ {FORMAT_SW_LONG, "elfu 1", new BigDecimal("1000")},
+ {FORMAT_SW_LONG, "elfu 3", new BigDecimal("3000")},
+ {FORMAT_SW_LONG, "elfu -3", new BigDecimal("-3000")},
+ {FORMAT_SW_LONG, "elfu 17", new BigDecimal("17000")},
+ {FORMAT_SW_LONG, "trilioni 12345679", new BigDecimal("12345679000000000000")},
+ {FORMAT_SW_LONG, "trilioni -12345679", new BigDecimal("-12345679000000000000")},
+ {FORMAT_SW_LONG, "elfu 599.01", new BigDecimal("599010.00")},
+ {FORMAT_SW_LONG, "elfu -599.01", new BigDecimal("-599010.00")},
+ {FORMAT_SE_SHORT, "999", new BigDecimal("999")},
+ {FORMAT_SE_SHORT, "8\u00a0mn", new BigDecimal("8000000")},
+ {FORMAT_SE_SHORT, "8\u00a0dt", new BigDecimal("8000")},
+ {FORMAT_SE_SHORT, "12345679\u00a0bn", new BigDecimal("12345679000000000000")},
+ {FORMAT_SE_SHORT, "12345679,89\u00a0bn", new BigDecimal("12345679890000000000.00")},
+ {FORMAT_SE_SHORT, "\u2212999", new BigDecimal("-999")},
+ {FORMAT_SE_SHORT, "\u22128\u00a0mn", new BigDecimal("-8000000")},
+ {FORMAT_SE_SHORT, "\u22128\u00a0dt", new BigDecimal("-8000")},
+ {FORMAT_SE_SHORT, "\u221212345679\u00a0bn", new BigDecimal("-12345679000000000000")},
+ {FORMAT_SE_SHORT, "\u221212345679,89\u00a0bn", new BigDecimal("-12345679890000000000.00")},};
+ }
+
+ @Test(dataProvider = "parse")
+ public void testParse(NumberFormat cnf, String parseString,
+ Number expected) throws ParseException {
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, BigDecimal.class);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestSpecialValues.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the formatting and parsing of special values
+ * @modules jdk.localedata
+ * @run testng/othervm TestSpecialValues
+ */
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestSpecialValues {
+
+ private static final NumberFormat FORMAT = NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+
+ @DataProvider(name = "formatSpecialValues")
+ Object[][] formatSpecialValues() {
+ return new Object[][]{
+ // number , formatted ouput
+ {+0.0, "0"},
+ {-0.0, "-0"},
+ {Double.MIN_VALUE, "0"},
+ {Double.MIN_NORMAL, "0"},
+ {Double.NaN, "NaN"},
+ {Double.POSITIVE_INFINITY, "\u221E"},
+ {Double.NEGATIVE_INFINITY, "-\u221E"},
+ {Long.MIN_VALUE, "-9223372T"},
+ {Long.MAX_VALUE, "9223372T"},};
+ }
+
+ @DataProvider(name = "parseSpecialValues")
+ Object[][] parseSpecialValues() {
+ return new Object[][]{
+ // parse string, parsed number
+ {"-0.0", -0.0},
+ {"" + Long.MIN_VALUE, Long.MIN_VALUE},
+ {"" + Long.MAX_VALUE, Long.MAX_VALUE},
+ {"NaN", Double.NaN},
+ {"\u221E", Double.POSITIVE_INFINITY},
+ {"-\u221E", Double.NEGATIVE_INFINITY},};
+ }
+
+ @Test(dataProvider = "formatSpecialValues")
+ public void testFormatSpecialValues(Object number, String expected) {
+ CompactFormatAndParseHelper.testFormat(FORMAT, number, expected);
+ }
+
+ @Test(dataProvider = "parseSpecialValues")
+ public void testParseSpecialValues(String parseString, Number expected)
+ throws ParseException {
+ CompactFormatAndParseHelper.testParse(FORMAT, parseString, expected, null, null);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestUExtensionOverride.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the behaviour of Unicode BCP 47 U Extension with
+ * compact number format
+ * @modules jdk.localedata
+ * @run testng/othervm TestUExtensionOverride
+ */
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestUExtensionOverride {
+
+ @DataProvider(name = "compactFormatData")
+ Object[][] compactFormatData() {
+ return new Object[][]{
+ // locale, number, formatted string
+
+ // -nu
+ {Locale.forLanguageTag("en-US-u-nu-deva"), 12345, "\u0967\u0968K"},
+ {Locale.forLanguageTag("en-US-u-nu-sinh"), 12345, "\u0de7\u0de8K"},
+ {Locale.forLanguageTag("en-US-u-nu-zzzz"), 12345, "12K"},
+ // -rg
+ {Locale.forLanguageTag("fr-FR-u-rg-cazzzz"), 1234567,
+ "1\u00a0234\u00a0567"},
+ {Locale.forLanguageTag("fr-FR-u-rg-cazzzz"), 1234567890,
+ "1\u00a0G"},
+ // -nu and -rg
+ {Locale.forLanguageTag("en-US-u-nu-deva-rg-dezzzz"), 12345,
+ "\u0967\u0968K"},
+ {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-cazzzz"), 1234567890,
+ "1\u00a0Md"},
+ {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-zzzz"), 12345,
+ "12\u00a0k"},
+ {Locale.forLanguageTag("fr-FR-u-rg-cazzzz-nu-deva"), 12345,
+ "\u0967\u0968\u00a0k"},};
+ }
+
+ @DataProvider(name = "compactParseData")
+ Object[][] compactParseData() {
+ return new Object[][]{
+ // locale, parse string, parsed number
+
+ // -nu
+ {Locale.forLanguageTag("en-US-u-nu-deva"),
+ "\u0967\u0968K", 12000L},
+ {Locale.forLanguageTag("en-US-u-nu-sinh"),
+ "\u0de7\u0de8K", 12000L},
+ {Locale.forLanguageTag("en-US-u-nu-zzzz"),
+ "12K", 12000L},
+ // -rg
+ {Locale.forLanguageTag("fr-FR-u-rg-cazzzz"),
+ "1\u00a0G", 1000000000L},
+ // -nu and -rg
+ {Locale.forLanguageTag("en-US-u-nu-deva-rg-dezzzz"),
+ "\u0967\u0968K", 12000L},
+ {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-cazzzz"),
+ "1\u00a0Md", 1000000000L},
+ {Locale.forLanguageTag("fr-FR-u-nu-zzzz-rg-zzzz"),
+ "12\u00a0k", 12000L},
+ {Locale.forLanguageTag("fr-FR-u-rg-cazzzz-nu-deva"),
+ "\u0967\u0968\u00a0k", 12000L},};
+ }
+
+ @Test(dataProvider = "compactFormatData")
+ public void testFormat(Locale locale, double num,
+ String expected) {
+ NumberFormat cnf = NumberFormat.getCompactNumberInstance(locale,
+ NumberFormat.Style.SHORT);
+ CompactFormatAndParseHelper.testFormat(cnf, num, expected);
+ }
+
+ @Test(dataProvider = "compactParseData")
+ public void testParse(Locale locale, String parseString,
+ Number expected) throws ParseException {
+ NumberFormat cnf = NumberFormat.getCompactNumberInstance(locale,
+ NumberFormat.Style.SHORT);
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/TestWithCompatProvider.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @summary Checks the compact number format with COMPAT provider. Since the
+ * compact number resources are only provided by CLDR, using COMPAT
+ * as a provider should always use the default patterns added in the
+ * FormatData.java resource bundle
+ * @modules jdk.localedata
+ * @run testng/othervm -Djava.locale.providers=COMPAT TestWithCompatProvider
+ */
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestWithCompatProvider {
+
+ private static final NumberFormat FORMAT_DZ_SHORT = NumberFormat
+ .getCompactNumberInstance(new Locale("dz"), NumberFormat.Style.SHORT);
+
+ private static final NumberFormat FORMAT_EN_US_SHORT = NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+
+ @DataProvider(name = "format")
+ Object[][] compactFormatData() {
+ return new Object[][]{
+ {FORMAT_DZ_SHORT, 1000.09, "1K"},
+ {FORMAT_DZ_SHORT, -999.99, "-1K"},
+ {FORMAT_DZ_SHORT, -0.0, "-0"},
+ {FORMAT_DZ_SHORT, new BigInteger("12345678901234567890"), "12345679T"},
+ {FORMAT_DZ_SHORT, new BigDecimal("12345678901234567890.89"), "12345679T"},
+ {FORMAT_EN_US_SHORT, -999.99, "-1K"},
+ {FORMAT_EN_US_SHORT, 9999, "10K"},
+ {FORMAT_EN_US_SHORT, 3000.90, "3K"},
+ {FORMAT_EN_US_SHORT, new BigInteger("12345678901234567890"), "12345679T"},
+ {FORMAT_EN_US_SHORT, new BigDecimal("12345678901234567890.89"), "12345679T"},};
+ }
+
+ @DataProvider(name = "parse")
+ Object[][] compactParseData() {
+ return new Object[][]{
+ {FORMAT_DZ_SHORT, "1K", 1000L},
+ {FORMAT_DZ_SHORT, "-3K", -3000L},
+ {FORMAT_DZ_SHORT, "12345700T", 1.23457E19},
+ {FORMAT_EN_US_SHORT, "-99", -99L},
+ {FORMAT_EN_US_SHORT, "10K", 10000L},
+ {FORMAT_EN_US_SHORT, "12345679T", 1.2345679E19},};
+ }
+
+ @Test(dataProvider = "format")
+ public void testFormat(NumberFormat cnf, Object number,
+ String expected) {
+ CompactFormatAndParseHelper.testFormat(cnf, number, expected);
+ }
+
+ @Test(dataProvider = "parse")
+ public void testParse(NumberFormat cnf, String parseString,
+ Number expected) throws ParseException {
+ CompactFormatAndParseHelper.testParse(cnf, parseString, expected, null, null);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/serialization/TestDeserializeCNF.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8177552
+ * @modules jdk.localedata
+ * @summary Checks deserialization of compact number format
+ * @library /java/text/testlib
+ * @build TestDeserializeCNF HexDumpReader
+ * @run testng/othervm TestDeserializeCNF
+ */
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.math.RoundingMode;
+import java.text.CompactNumberFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import static org.testng.Assert.*;
+
+public class TestDeserializeCNF {
+
+ // This object is serialized in cnf1.ser.txt with HALF_UP
+ // rounding mode, groupingsize = 3 and parseBigDecimal = true
+ private static final CompactNumberFormat COMPACT_FORMAT1 = new CompactNumberFormat("#,##0.###",
+ DecimalFormatSymbols.getInstance(Locale.US),
+ new String[]{"", "", "", "0K", "00K", "000K", "0M", "00M", "000M", "0B", "00B", "000B", "0T", "00T", "000T"});
+
+ // This object is serialized in cnf2.ser.txt with min integer digits = 20
+ // and min fraction digits = 5
+ private static final CompactNumberFormat COMPACT_FORMAT2 = new CompactNumberFormat("#,##0.###",
+ DecimalFormatSymbols.getInstance(Locale.JAPAN),
+ new String[]{"", "", "", "0", "0\u4e07", "00\u4e07", "000\u4e07", "0000\u4e07", "0\u5104", "00\u5104", "000\u5104", "0000\u5104", "0\u5146", "00\u5146", "000\u5146"});
+
+ private static final String FILE_COMPACT_FORMAT1 = "cnf1.ser.txt";
+ private static final String FILE_COMPACT_FORMAT2 = "cnf2.ser.txt";
+
+ @BeforeTest
+ public void mutateInstances() {
+ COMPACT_FORMAT1.setRoundingMode(RoundingMode.HALF_UP);
+ COMPACT_FORMAT1.setGroupingSize(3);
+ COMPACT_FORMAT1.setParseBigDecimal(true);
+
+ COMPACT_FORMAT2.setMinimumIntegerDigits(20);
+ COMPACT_FORMAT2.setMinimumFractionDigits(5);
+ }
+
+ @Test
+ public void testDeserialization() throws IOException, ClassNotFoundException {
+ try (InputStream istream1 = HexDumpReader.getStreamFromHexDump(FILE_COMPACT_FORMAT1);
+ ObjectInputStream ois1 = new ObjectInputStream(istream1);
+ InputStream istream2 = HexDumpReader.getStreamFromHexDump(FILE_COMPACT_FORMAT2);
+ ObjectInputStream ois2 = new ObjectInputStream(istream2);) {
+
+ CompactNumberFormat obj1 = (CompactNumberFormat) ois1.readObject();
+ assertEquals(obj1, COMPACT_FORMAT1, "Deserialized instance is not"
+ + " equal to the instance serialized in " + FILE_COMPACT_FORMAT1);
+
+ CompactNumberFormat obj2 = (CompactNumberFormat) ois2.readObject();
+ assertEquals(obj2, COMPACT_FORMAT2, "Deserialized instance is not"
+ + " equal to the instance serialized in " + FILE_COMPACT_FORMAT2);
+ }
+ }
+
+ // The objects are serialized using the serialize() method, the hex
+ // dump printed is copied to respective object files
+// void serialize(CompactNumberFormat cnf) {
+// ByteArrayOutputStream baos = new ByteArrayOutputStream();
+// try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+// oos.writeObject(cnf);
+// } catch (IOException ioe) {
+// throw new RuntimeException(ioe);
+// }
+// byte[] ser = baos.toByteArray();
+// for (byte b : ser) {
+// System.out.print("" + String.format("%02x", b));
+// }
+// }
+
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/serialization/TestSerialization.java Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/*
+ * @test
+ * @bug 8177552
+ * @modules jdk.localedata
+ * @summary Checks the serialization feature of CompactNumberFormat
+ * @run testng/othervm TestSerialization
+ */
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.RoundingMode;
+import java.text.CompactNumberFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
+import static org.testng.Assert.*;
+
+public class TestSerialization {
+
+ private static final NumberFormat FORMAT_HI = NumberFormat.getCompactNumberInstance(
+ new Locale("hi"), NumberFormat.Style.SHORT);
+ private static final CompactNumberFormat FORMAT_EN_US = (CompactNumberFormat) NumberFormat
+ .getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
+ private static final NumberFormat FORMAT_JA_JP = NumberFormat.getCompactNumberInstance(
+ Locale.JAPAN, NumberFormat.Style.SHORT);
+ private static final NumberFormat FORMAT_FR_FR = NumberFormat.getCompactNumberInstance(
+ Locale.FRANCE, NumberFormat.Style.LONG);
+ private static final NumberFormat FORMAT_DE_DE = NumberFormat.getCompactNumberInstance(
+ Locale.GERMANY, NumberFormat.Style.SHORT);
+ private static final NumberFormat FORMAT_KO_KR = NumberFormat.getCompactNumberInstance(
+ Locale.KOREA, NumberFormat.Style.SHORT);
+
+ @BeforeTest
+ public void mutateInstances() {
+ FORMAT_HI.setMinimumFractionDigits(2);
+ FORMAT_HI.setMinimumIntegerDigits(5);
+
+ FORMAT_EN_US.setRoundingMode(RoundingMode.HALF_UP);
+ FORMAT_EN_US.setGroupingSize(3);
+ FORMAT_EN_US.setParseBigDecimal(true);
+
+ FORMAT_JA_JP.setMaximumFractionDigits(30);
+ FORMAT_JA_JP.setMaximumIntegerDigits(30);
+
+ FORMAT_FR_FR.setParseIntegerOnly(true);
+ FORMAT_FR_FR.setGroupingUsed(true);
+
+ // Setting minimum integer digits beyond the allowed range
+ FORMAT_DE_DE.setMinimumIntegerDigits(320);
+
+ // Setting minimum fraction digits beyond the allowed range
+ FORMAT_KO_KR.setMinimumFractionDigits(350);
+ }
+
+ @Test
+ public void testSerialization() throws IOException, ClassNotFoundException {
+ // Serialize
+ serialize("cdf.ser", FORMAT_HI, FORMAT_EN_US, FORMAT_JA_JP, FORMAT_FR_FR, FORMAT_DE_DE, FORMAT_KO_KR);
+ // Deserialize
+ deserialize("cdf.ser", FORMAT_HI, FORMAT_EN_US, FORMAT_JA_JP, FORMAT_FR_FR, FORMAT_DE_DE, FORMAT_KO_KR);
+ }
+
+ private void serialize(String fileName, NumberFormat... formats)
+ throws IOException {
+ try (ObjectOutputStream os = new ObjectOutputStream(
+ new FileOutputStream(fileName))) {
+ for (NumberFormat fmt : formats) {
+ os.writeObject(fmt);
+ }
+ }
+ }
+
+ private static void deserialize(String fileName, NumberFormat... formats)
+ throws IOException, ClassNotFoundException {
+ try (ObjectInputStream os = new ObjectInputStream(
+ new FileInputStream(fileName))) {
+ for (NumberFormat fmt : formats) {
+ NumberFormat obj = (NumberFormat) os.readObject();
+ assertEquals(fmt, obj, "Serialized and deserialized"
+ + " objects do not match");
+
+ long number = 123456789789L;
+ String expected = fmt.format(number);
+ String actual = obj.format(number);
+ assertEquals(actual, expected, "Serialized and deserialized"
+ + " objects are expected to return same formatted"
+ + " output for number: " + number);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/serialization/cnf1.ser.txt Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# Hex dump of a serialized CompactNumberFormat for TestDeserializeCNF.
+
+aced00057372001d6a6176612e746578742e436f6d706163744e756d626572466f726d
+617462ed0c37b8207cf602000642000c67726f7570696e6753697a655a000f70617273
+65426967446563696d616c5b000f636f6d706163745061747465726e737400135b4c6a
+6176612f6c616e672f537472696e673b4c000e646563696d616c5061747465726e7400
+124c6a6176612f6c616e672f537472696e673b4c000c726f756e64696e674d6f646574
+00184c6a6176612f6d6174682f526f756e64696e674d6f64653b4c000773796d626f6c
+737400204c6a6176612f746578742f446563696d616c466f726d617453796d626f6c73
+3b787200166a6176612e746578742e4e756d626572466f726d6174dff6b3bf137d07e8
+03000b5a000c67726f7570696e67557365644200116d61784672616374696f6e446967
+6974734200106d6178496e74656765724469676974734900156d6178696d756d467261
+6374696f6e4469676974734900146d6178696d756d496e746567657244696769747342
+00116d696e4672616374696f6e4469676974734200106d696e496e7465676572446967
+6974734900156d696e696d756d4672616374696f6e4469676974734900146d696e696d
+756d496e74656765724469676974735a00107061727365496e74656765724f6e6c7949
+001573657269616c56657273696f6e4f6e53747265616d787200106a6176612e746578
+742e466f726d6174fbd8bc12e90f1843020000787000007f0000000000000135000100
+000000000000010000000001780301757200135b4c6a6176612e6c616e672e53747269
+6e673badd256e7e91d7b4702000078700000000f74000071007e000a71007e000a7400
+02304b74000330304b7400043030304b740002304d74000330304d7400043030304d74
+0002304274000330304274000430303042740002305474000330305474000430303054
+740009232c2323302e2323237e7200166a6176612e6d6174682e526f756e64696e674d
+6f646500000000000000001200007872000e6a6176612e6c616e672e456e756d000000
+0000000000120000787074000748414c465f55507372001e6a6176612e746578742e44
+6563696d616c466f726d617453796d626f6c73501d17990868939c0200114300106465
+63696d616c536570617261746f72430005646967697443000b6578706f6e656e746961
+6c43001167726f7570696e67536570617261746f724300096d696e75735369676e4300
+116d6f6e6574617279536570617261746f724300107061747465726e53657061726174
+6f724300077065724d696c6c43000770657263656e7449001573657269616c56657273
+696f6e4f6e53747265616d4300097a65726f44696769744c00034e614e71007e00024c
+000e63757272656e637953796d626f6c71007e00024c00146578706f6e656e7469616c
+536570617261746f7271007e00024c0008696e66696e69747971007e00024c0012696e
+746c43757272656e637953796d626f6c71007e00024c00066c6f63616c657400124c6a
+6176612f7574696c2f4c6f63616c653b7870002e00230045002c002d002e003b203000
+250000000300307400034e614e7074000145740003e2889e70737200106a6176612e75
+74696c2e4c6f63616c657ef811609c30f9ec03000649000868617368636f64654c0007
+636f756e74727971007e00024c000a657874656e73696f6e7371007e00024c00086c61
+6e677561676571007e00024c000673637269707471007e00024c000776617269616e74
+71007e00027870ffffffff740002555371007e000a740002656e71007e000a71007e00
+0a78
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/text/Format/CompactNumberFormat/serialization/cnf2.ser.txt Thu Dec 06 12:39:28 2018 +0530
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# Hex dump of a serialized CompactNumberFormat for TestDeserializeCNF.
+
+aced00057372001d6a6176612e746578742e436f6d706163744e756d626572466f726d
+617462ed0c37b8207cf602000642000c67726f7570696e6753697a655a000f70617273
+65426967446563696d616c5b000f636f6d706163745061747465726e737400135b4c6a
+6176612f6c616e672f537472696e673b4c000e646563696d616c5061747465726e7400
+124c6a6176612f6c616e672f537472696e673b4c000c726f756e64696e674d6f646574
+00184c6a6176612f6d6174682f526f756e64696e674d6f64653b4c000773796d626f6c
+737400204c6a6176612f746578742f446563696d616c466f726d617453796d626f6c73
+3b787200166a6176612e746578742e4e756d626572466f726d6174dff6b3bf137d07e8
+03000b5a000c67726f7570696e67557365644200116d61784672616374696f6e446967
+6974734200106d6178496e74656765724469676974734900156d6178696d756d467261
+6374696f6e4469676974734900146d6178696d756d496e746567657244696769747342
+00116d696e4672616374696f6e4469676974734200106d696e496e7465676572446967
+6974734900156d696e696d756d4672616374696f6e4469676974734900146d696e696d
+756d496e74656765724469676974735a00107061727365496e74656765724f6e6c7949
+001573657269616c56657273696f6e4f6e53747265616d787200106a6176612e746578
+742e466f726d6174fbd8bc12e90f1843020000787000057f0000000500000135051400
+000005000000140000000001780000757200135b4c6a6176612e6c616e672e53747269
+6e673badd256e7e91d7b4702000078700000000f74000071007e000a71007e000a7400
+013074000430e4b8877400053030e4b887740006303030e4b88774000730303030e4b8
+8774000430e584847400053030e58484740006303030e5848474000730303030e58484
+74000430e585867400053030e58586740006303030e58586740009232c2323302e2323
+237e7200166a6176612e6d6174682e526f756e64696e674d6f64650000000000000000
+1200007872000e6a6176612e6c616e672e456e756d0000000000000000120000787074
+000948414c465f4556454e7372001e6a6176612e746578742e446563696d616c466f72
+6d617453796d626f6c73501d17990868939c020011430010646563696d616c53657061
+7261746f72430005646967697443000b6578706f6e656e7469616c43001167726f7570
+696e67536570617261746f724300096d696e75735369676e4300116d6f6e6574617279
+536570617261746f724300107061747465726e536570617261746f724300077065724d
+696c6c43000770657263656e7449001573657269616c56657273696f6e4f6e53747265
+616d4300097a65726f44696769744c00034e614e71007e00024c000e63757272656e63
+7953796d626f6c71007e00024c00146578706f6e656e7469616c536570617261746f72
+71007e00024c0008696e66696e69747971007e00024c0012696e746c43757272656e63
+7953796d626f6c71007e00024c00066c6f63616c657400124c6a6176612f7574696c2f
+4c6f63616c653b7870002e00230045002c002d002e003b203000250000000300307400
+034e614e7074000145740003e2889e70737200106a6176612e7574696c2e4c6f63616c
+657ef811609c30f9ec03000649000868617368636f64654c0007636f756e7472797100
+7e00024c000a657874656e73696f6e7371007e00024c00086c616e677561676571007e
+00024c000673637269707471007e00024c000776617269616e7471007e00027870ffff
+ffff7400024a5071007e000a7400026a6171007e000a71007e000a78